1package imapserver
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/mjl-/mox/store"
8)
9
10type numSet struct {
11 searchResult bool // "$"
12 ranges []numRange
13}
14
15type numRange struct {
16 first setNumber
17 last *setNumber // if nil, this numRange is just a setNumber in "first" and first.star will be false
18}
19
20type setNumber struct {
21 number uint32
22 star bool // References last message (max sequence number/uid). ../rfc/9051:799
23}
24
25// containsSeq returns whether seq is in the numSet, given uids and (saved) searchResult.
26// uids and searchResult must be sorted. searchResult can have uids that are no longer in uids.
27func (ss numSet) containsSeq(seq msgseq, uids []store.UID, searchResult []store.UID) bool {
28 if len(uids) == 0 {
29 return false
30 }
31 if ss.searchResult {
32 uid := uids[int(seq)-1]
33 return uidSearch(searchResult, uid) > 0 && uidSearch(uids, uid) > 0
34 }
35 return ss.containsSeqCount(seq, uint32(len(uids)))
36}
37
38// containsSeqCount returns whether seq is contained in ss, which must not be a searchResult, assuming the message count.
39func (ss numSet) containsSeqCount(seq msgseq, msgCount uint32) bool {
40 if msgCount == 0 {
41 return false
42 }
43 for _, r := range ss.ranges {
44 first := r.first.number
45 if r.first.star || first > msgCount {
46 first = msgCount
47 }
48
49 last := first
50 if r.last != nil {
51 last = r.last.number
52 if r.last.star || last > msgCount {
53 last = msgCount
54 }
55 }
56 if first > last {
57 first, last = last, first
58 }
59
60 if uint32(seq) >= first && uint32(seq) <= last {
61 return true
62 }
63 }
64 return false
65}
66
67func (ss numSet) containsUID(uid store.UID, uids []store.UID, searchResult []store.UID) bool {
68 if len(uids) == 0 {
69 return false
70 }
71 if ss.searchResult {
72 return uidSearch(searchResult, uid) > 0 && uidSearch(uids, uid) > 0
73 }
74 for _, r := range ss.ranges {
75 first := store.UID(r.first.number)
76 if r.first.star || first > uids[len(uids)-1] {
77 first = uids[len(uids)-1]
78 }
79 last := first
80 // Num in <num>:* can be larger than last, but it still matches the last...
81 // Similar for *:<num>. ../rfc/9051:4814
82 if r.last != nil {
83 last = store.UID(r.last.number)
84 if r.last.star || last > uids[len(uids)-1] {
85 last = uids[len(uids)-1]
86 }
87 }
88 if first > last {
89 first, last = last, first
90 }
91 if uid >= first && uid <= last && uidSearch(uids, uid) > 0 {
92 return true
93 }
94 }
95 return false
96}
97
98// containsKnownUID returns whether uid, which is known to exist, matches the numSet.
99// highestUID must return the highest/last UID in the mailbox, or an error. A last UID must
100// exist, otherwise this method wouldn't have been called with a known uid.
101// highestUID is needed for interpreting UID sets like "<num>:*" where num is
102// higher than the uid to check.
103func (ss numSet) containsKnownUID(uid store.UID, searchResult []store.UID, highestUID func() (store.UID, error)) (bool, error) {
104 if ss.searchResult {
105 return uidSearch(searchResult, uid) > 0, nil
106 }
107
108 for _, r := range ss.ranges {
109 a := store.UID(r.first.number)
110 // Num in <num>:* can be larger than last, but it still matches the last...
111 // Similar for *:<num>. ../rfc/9051:4814
112 if r.first.star {
113 if r.last != nil && uid >= store.UID(r.last.number) {
114 return true, nil
115 }
116
117 var err error
118 a, err = highestUID()
119 if err != nil {
120 return false, err
121 }
122 }
123 b := a
124 if r.last != nil {
125 b = store.UID(r.last.number)
126 if r.last.star {
127 if uid >= a {
128 return true, nil
129 }
130
131 var err error
132 b, err = highestUID()
133 if err != nil {
134 return false, err
135 }
136 }
137 }
138 if a > b {
139 a, b = b, a
140 }
141 if uid >= a && uid <= b {
142 return true, nil
143 }
144 }
145 return false, nil
146}
147
148// contains returns whether the numset contains the number.
149// only allowed on basic, strictly increasing numsets.
150func (ss numSet) contains(v uint32) bool {
151 for _, r := range ss.ranges {
152 if r.first.number == v || r.last != nil && v > r.first.number && v <= r.last.number {
153 return true
154 }
155 }
156 return false
157}
158
159func (ss numSet) empty() bool {
160 return !ss.searchResult && len(ss.ranges) == 0
161}
162
163// Strings returns the numset in zero or more strings of maxSize bytes. If
164// maxSize is <= 0, a single string is returned.
165func (ss numSet) Strings(maxSize int) []string {
166 if ss.searchResult {
167 return []string{"$"}
168 }
169 var l []string
170 var line string
171 for _, r := range ss.ranges {
172 s := ""
173 if r.first.star {
174 s += "*"
175 } else {
176 s += fmt.Sprintf("%d", r.first.number)
177 }
178 if r.last == nil {
179 if r.first.star {
180 panic("invalid numSet range first star without last")
181 }
182 } else {
183 s += ":"
184 if r.last.star {
185 s += "*"
186 } else {
187 s += fmt.Sprintf("%d", r.last.number)
188 }
189 }
190
191 nsize := len(line) + len(s)
192 if line != "" {
193 nsize++ // comma
194 }
195 if maxSize > 0 && nsize > maxSize {
196 l = append(l, line)
197 line = s
198 continue
199 }
200 if line != "" {
201 line += ","
202 }
203 line += s
204 }
205 if line != "" {
206 l = append(l, line)
207 }
208 return l
209}
210
211func (ss numSet) String() string {
212 l := ss.Strings(0)
213 if len(l) == 0 {
214 return ""
215 }
216 return l[0]
217}
218
219// interpretStar returns a numset that interprets stars in a numset, returning a new
220// numset without stars with increasing first/last.
221func (s numSet) interpretStar(uids []store.UID) numSet {
222 var ns numSet
223 if len(uids) == 0 {
224 return ns
225 }
226
227 for _, r := range s.ranges {
228 first := r.first.number
229 if r.first.star || first > uint32(uids[len(uids)-1]) {
230 first = uint32(uids[len(uids)-1])
231 }
232 last := first
233 if r.last != nil {
234 last = r.last.number
235 if r.last.star || last > uint32(uids[len(uids)-1]) {
236 last = uint32(uids[len(uids)-1])
237 }
238 }
239 if first > last {
240 first, last = last, first
241 }
242 nr := numRange{first: setNumber{number: first}}
243 if first != last {
244 nr.last = &setNumber{number: last}
245 }
246 ns.ranges = append(ns.ranges, nr)
247 }
248 return ns
249}
250
251// whether numSet only has numbers (no star/search), and is strictly increasing.
252func (s *numSet) isBasicIncreasing() bool {
253 if s.searchResult {
254 return false
255 }
256 var last uint32
257 for _, r := range s.ranges {
258 if r.first.star || r.first.number <= last || r.last != nil && (r.last.star || r.last.number < r.first.number) {
259 return false
260 }
261 last = r.first.number
262 if r.last != nil {
263 last = r.last.number
264 }
265 }
266 return true
267}
268
269type numIter struct {
270 s numSet
271 i int
272 r *rangeIter
273}
274
275// newIter must only be called on a numSet that is basic (no star/search) and ascending.
276func (s numSet) newIter() *numIter {
277 return &numIter{s: s}
278}
279
280func (i *numIter) Next() (uint32, bool) {
281 if v, ok := i.r.Next(); ok {
282 return v, ok
283 }
284 if i.i >= len(i.s.ranges) {
285 return 0, false
286 }
287 i.r = i.s.ranges[i.i].newIter()
288 i.i++
289 return i.r.Next()
290}
291
292type rangeIter struct {
293 r numRange
294 o int
295}
296
297// newIter must only be called on a range in a numSet that is basic (no star/search) and ascending.
298func (r numRange) newIter() *rangeIter {
299 return &rangeIter{r: r, o: 0}
300}
301
302func (r *rangeIter) Next() (uint32, bool) {
303 if r == nil {
304 return 0, false
305 }
306 if r.o == 0 {
307 r.o++
308 return r.r.first.number, true
309 }
310 if r.r.last == nil || r.r.first.number+uint32(r.o) > r.r.last.number {
311 return 0, false
312 }
313 v := r.r.first.number + uint32(r.o)
314 r.o++
315 return v, true
316}
317
318// append adds a new number to the set, extending a range, or starting a new one (possibly the first).
319// can only be used on basic numsets, without star/searchResult.
320func (s *numSet) append(v uint32) {
321 if len(s.ranges) == 0 {
322 s.ranges = []numRange{{first: setNumber{number: v}}}
323 return
324 }
325 ri := len(s.ranges) - 1
326 r := s.ranges[ri]
327 if v == r.first.number+1 && r.last == nil {
328 s.ranges[ri].last = &setNumber{number: v}
329 } else if r.last != nil && v == r.last.number+1 {
330 r.last.number++
331 } else {
332 s.ranges = append(s.ranges, numRange{first: setNumber{number: v}})
333 }
334}
335
336type partial struct {
337 offset uint32
338 count uint32
339}
340
341type sectionPart struct {
342 part []uint32
343 text *sectionText
344}
345
346type sectionText struct {
347 mime bool // if "MIME"
348 msgtext *sectionMsgtext
349}
350
351// a non-nil *sectionSpec with nil msgtext & nil part means there were []'s, but nothing inside. e.g. "BODY[]".
352type sectionSpec struct {
353 msgtext *sectionMsgtext
354 part *sectionPart
355}
356
357type sectionMsgtext struct {
358 s string // "HEADER", "HEADER.FIELDS", "HEADER.FIELDS.NOT", "TEXT"
359 headers []string // for "HEADER.FIELDS"*
360}
361
362type fetchAtt struct {
363 field string // uppercase, eg "ENVELOPE", "BODY". ".PEEK" is removed.
364 peek bool
365 section *sectionSpec
366 sectionBinary []uint32
367 partial *partial
368 previewLazy bool // Not regular "PREVIEW", but "PREVIEW (LAZY)".
369}
370
371type searchKey struct {
372 // Only one of searchKeys, seqSet and op can be non-nil/non-empty.
373 searchKeys []searchKey // In case of nested/multiple keys. Also for the top-level command.
374 seqSet *numSet // In case of bare sequence set. For op UID, field uidSet contains the parameter.
375 op string // Determines which of the fields below are set.
376
377 headerField string
378 astring string
379 date time.Time
380 atom string
381 number int64
382 searchKey *searchKey
383 searchKey2 *searchKey
384 uidSet numSet
385 clientModseq *int64
386}
387
388func compactUIDSet(l []store.UID) (r numSet) {
389 for len(l) > 0 {
390 e := 1
391 for ; e < len(l) && l[e] == l[e-1]+1; e++ {
392 }
393 first := setNumber{number: uint32(l[0])}
394 var last *setNumber
395 if e > 1 {
396 last = &setNumber{number: uint32(l[e-1])}
397 }
398 r.ranges = append(r.ranges, numRange{first, last})
399 l = l[e:]
400 }
401 return
402}
403