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