9	"github.com/mjl-/mox/imapclient"
 
12var searchMsg = strings.ReplaceAll(`Date: Mon, 1 Jan 2022 10:00:00 +0100 (CEST)
 
13From: mjl <mjl@mox.example>
 
15To: mox <mox@mox.example>
 
18Reply-To: <noreply@mox.example>
 
19Message-Id: <123@mox.example>
 
21Content-Type: multipart/alternative; boundary=x
 
24Content-Type: text/plain; charset=utf-8
 
29Content-Type: text/html; charset=utf-8
 
36func (tc *testconn) xsearch(nums ...uint32) {
 
39	tc.xuntagged(imapclient.UntaggedSearch(nums))
 
42func (tc *testconn) xsearchmodseq(modseq int64, nums ...uint32) {
 
49	tc.xuntagged(imapclient.UntaggedSearchModSeq{Nums: nums, ModSeq: modseq})
 
52func (tc *testconn) xesearch(exp imapclient.UntaggedEsearch) {
 
55	exp.Correlator = tc.client.LastTag
 
59func TestSearch(t *testing.T) {
 
62	tc.client.Login("mjl@mox.example", "testtest")
 
63	tc.client.Select("inbox")
 
65	// Add 5 and delete first 4 messages. So UIDs start at 5.
 
66	received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC)
 
67	for i := 0; i < 5; i++ {
 
68		tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
 
70	tc.client.StoreFlagsSet("1:4", true, `\Deleted`)
 
73	received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
 
74	tc.client.Append("inbox", nil, &received, []byte(searchMsg))
 
76	received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
 
77	mostFlags := []string{
 
91	tc.client.Append("inbox", mostFlags, &received, []byte(searchMsg))
 
93	// We now have sequence numbers 1,2,3 and UIDs 5,6,7.
 
95	tc.transactf("ok", "search all")
 
98	tc.transactf("ok", "uid search all")
 
101	tc.transactf("ok", "search answered")
 
104	tc.transactf("ok", `search bcc "bcc@mox.example"`)
 
107	tc.transactf("ok", "search before 1-Jan-2038")
 
109	tc.transactf("ok", "search before 1-Jan-2020")
 
110	tc.xsearch() // Before is about received, not date header of message.
 
112	tc.transactf("ok", `search body "Joe"`)
 
114	tc.transactf("ok", `search body "Joe" body "bogus"`)
 
116	tc.transactf("ok", `search body "Joe" text "Blurdybloop"`)
 
118	tc.transactf("ok", `search body "Joe" not text "mox"`)
 
120	tc.transactf("ok", `search body "Joe" not not body "Joe"`)
 
122	tc.transactf("ok", `search body "this is plain text"`)
 
124	tc.transactf("ok", `search body "this is html"`)
 
127	tc.transactf("ok", `search cc "xcc@mox.example"`)
 
130	tc.transactf("ok", `search deleted`)
 
133	tc.transactf("ok", `search flagged`)
 
136	tc.transactf("ok", `search from "foobar@Blurdybloop.example"`)
 
139	tc.transactf("ok", `search keyword $Forwarded`)
 
142	tc.transactf("ok", `search keyword Custom1`)
 
145	tc.transactf("ok", `search keyword custom2`)
 
148	tc.transactf("ok", `search new`)
 
149	tc.xsearch() // New requires a message to be recent. We pretend all messages are not recent.
 
151	tc.transactf("ok", `search old`)
 
154	tc.transactf("ok", `search on 1-Jan-2022`)
 
157	tc.transactf("ok", `search recent`)
 
160	tc.transactf("ok", `search seen`)
 
163	tc.transactf("ok", `search since 1-Jan-2020`)
 
166	tc.transactf("ok", `search subject "afternoon"`)
 
169	tc.transactf("ok", `search text "Joe"`)
 
172	tc.transactf("ok", `search to "mooch@owatagu.siam.edu.example"`)
 
175	tc.transactf("ok", `search unanswered`)
 
178	tc.transactf("ok", `search undeleted`)
 
181	tc.transactf("ok", `search unflagged`)
 
184	tc.transactf("ok", `search unkeyword $Junk`)
 
187	tc.transactf("ok", `search unkeyword custom1`)
 
190	tc.transactf("ok", `search unseen`)
 
193	tc.transactf("ok", `search draft`)
 
196	tc.transactf("ok", `search header "subject" "afternoon"`)
 
199	tc.transactf("ok", `search larger 1`)
 
202	tc.transactf("ok", `search not text "mox"`)
 
205	tc.transactf("ok", `search or seen unseen`)
 
208	tc.transactf("ok", `search or unseen seen`)
 
211	tc.transactf("ok", `search sentbefore 8-Feb-1994`)
 
214	tc.transactf("ok", `search senton 7-Feb-1994`)
 
217	tc.transactf("ok", `search sentsince 6-Feb-1994`)
 
220	tc.transactf("ok", `search smaller 9999999`)
 
223	tc.transactf("ok", `search uid 1`)
 
226	tc.transactf("ok", `search uid 5`)
 
229	tc.transactf("ok", `search or larger 1000000 smaller 1`)
 
232	tc.transactf("ok", `search undraft`)
 
235	tc.transactf("no", `search charset unknown text "mox"`)
 
236	tc.transactf("ok", `search charset us-ascii text "mox"`)
 
238	tc.transactf("ok", `search charset utf-8 text "mox"`)
 
241	esearchall := func(ss string) imapclient.UntaggedEsearch {
 
242		return imapclient.UntaggedEsearch{All: esearchall0(ss)}
 
245	uint32ptr := func(v uint32) *uint32 {
 
249	// Do new-style ESEARCH requests with RETURN. We should get an ESEARCH response.
 
250	tc.transactf("ok", "search return () all")
 
251	tc.xesearch(esearchall("1:3")) // Without any options, "ALL" is implicit.
 
253	tc.transactf("ok", "search return (min max count all) all")
 
254	tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(3), All: esearchall0("1:3")})
 
256	tc.transactf("ok", "UID search return (min max count all) all")
 
257	tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(3), All: esearchall0("5:7")})
 
259	tc.transactf("ok", "search return (min) all")
 
260	tc.xesearch(imapclient.UntaggedEsearch{Min: 1})
 
262	tc.transactf("ok", "search return (min) 3")
 
263	tc.xesearch(imapclient.UntaggedEsearch{Min: 3})
 
265	tc.transactf("ok", "search return (min) NOT all")
 
266	tc.xesearch(imapclient.UntaggedEsearch{}) // Min not present if no match.
 
268	tc.transactf("ok", "search return (max) all")
 
269	tc.xesearch(imapclient.UntaggedEsearch{Max: 3})
 
271	tc.transactf("ok", "search return (max) 1")
 
272	tc.xesearch(imapclient.UntaggedEsearch{Max: 1})
 
274	tc.transactf("ok", "search return (max) not all")
 
275	tc.xesearch(imapclient.UntaggedEsearch{}) // Max not present if no match.
 
277	tc.transactf("ok", "search return (min max) all")
 
278	tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3})
 
280	tc.transactf("ok", "search return (min max) 1")
 
281	tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 1})
 
283	tc.transactf("ok", "search return (min max) not all")
 
284	tc.xesearch(imapclient.UntaggedEsearch{})
 
286	tc.transactf("ok", "search return (all) not all")
 
287	tc.xesearch(imapclient.UntaggedEsearch{}) // All not present if no match.
 
289	tc.transactf("ok", "search return (min max all) not all")
 
290	tc.xesearch(imapclient.UntaggedEsearch{})
 
292	tc.transactf("ok", "search return (min max all count) not all")
 
293	tc.xesearch(imapclient.UntaggedEsearch{Count: uint32ptr(0)})
 
295	tc.transactf("ok", "search return (min max count all) 1,3")
 
296	tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(2), All: esearchall0("1,3")})
 
298	tc.transactf("ok", "search return (min max count all) UID 5,7")
 
299	tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(2), All: esearchall0("1,3")})
 
301	tc.transactf("ok", "uid search return (min max count all) 1,3")
 
302	tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(2), All: esearchall0("5,7")})
 
304	tc.transactf("ok", "uid search return (min max count all) UID 5,7")
 
305	tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(2), All: esearchall0("5,7")})
 
307	tc.transactf("no", `search return () charset unknown text "mox"`)
 
308	tc.transactf("ok", `search return () charset us-ascii text "mox"`)
 
309	tc.xesearch(esearchall("2:3"))
 
310	tc.transactf("ok", `search return () charset utf-8 text "mox"`)
 
311	tc.xesearch(esearchall("2:3"))
 
313	tc.transactf("bad", `search return (unknown) all`)
 
315	tc.transactf("ok", "search return (save) 2")
 
317	tc.transactf("ok", "fetch $ (uid)")
 
318	tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(6)}})
 
320	tc.transactf("ok", "search return (all) $")
 
321	tc.xesearch(esearchall("2"))
 
323	tc.transactf("ok", "search return (save) $")
 
326	tc.transactf("ok", "search return (save all) all")
 
327	tc.xesearch(esearchall("1:3"))
 
329	tc.transactf("ok", "search return (all save) all")
 
330	tc.xesearch(esearchall("1:3"))
 
332	tc.transactf("ok", "search return (min save) all")
 
333	tc.xesearch(imapclient.UntaggedEsearch{Min: 1})
 
334	tc.transactf("ok", "fetch $ (uid)")
 
335	tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(5)}})
 
337	// Do a seemingly old-style search command with IMAP4rev2 enabled. We'll still get ESEARCH responses.
 
338	tc.client.Enable("IMAP4rev2")
 
339	tc.transactf("ok", `search undraft`)
 
340	tc.xesearch(esearchall("1:2"))
 
343// esearchall makes an UntaggedEsearch response with All set, for comparisons.
 
344func esearchall0(ss string) imapclient.NumSet {
 
345	seqset := imapclient.NumSet{}
 
346	for _, rs := range strings.Split(ss, ",") {
 
347		t := strings.Split(rs, ":")
 
354			v, err := strconv.ParseUint(t[0], 10, 32)
 
362				v, err := strconv.ParseUint(t[1], 10, 32)
 
370		seqset.Ranges = append(seqset.Ranges, imapclient.NumRange{First: first, Last: last})