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