1package imapserver
2
3import (
4 "errors"
5 "fmt"
6 "net/textproto"
7 "strconv"
8 "strings"
9 "time"
10)
11
12var (
13 listWildcards = "%*"
14 char = charRange('\x01', '\x7f')
15 ctl = charRange('\x01', '\x19')
16 quotedSpecials = `"\`
17 respSpecials = "]"
18 atomChar = charRemove(char, "(){ "+ctl+listWildcards+quotedSpecials+respSpecials)
19 astringChar = atomChar + respSpecials
20)
21
22func charRange(first, last rune) string {
23 r := ""
24 c := first
25 r += string(c)
26 for c < last {
27 c++
28 r += string(c)
29 }
30 return r
31}
32
33func charRemove(s, remove string) string {
34 r := ""
35next:
36 for _, c := range s {
37 for _, x := range remove {
38 if c == x {
39 continue next
40 }
41 }
42 r += string(c)
43 }
44 return r
45}
46
47type parser struct {
48 // Orig is the line in original casing, and upper in upper casing. We often match
49 // against upper for easy case insensitive handling as IMAP requires, but sometimes
50 // return from orig to keep the original case.
51 orig string
52 upper string
53 o int // Current offset in parsing.
54 contexts []string // What we're parsing, for error messages.
55 literals int // Literals in command, for limit.
56 literalSize int64 // Total size of literals in command, for limit.
57 conn *conn
58}
59
60// toUpper upper cases bytes that are a-z. strings.ToUpper does too much. and
61// would replace invalid bytes with unicode replacement characters, which would
62// break our requirement that offsets into the original and upper case strings
63// point to the same character.
64func toUpper(s string) string {
65 r := []byte(s)
66 for i, c := range r {
67 if c >= 'a' && c <= 'z' {
68 r[i] = c - 0x20
69 }
70 }
71 return string(r)
72}
73
74func newParser(s string, conn *conn) *parser {
75 return &parser{s, toUpper(s), 0, nil, 0, 0, conn}
76}
77
78func (p *parser) xerrorf(format string, args ...any) {
79 var err error
80 errmsg := fmt.Sprintf(format, args...)
81 remaining := fmt.Sprintf("remaining %q", p.orig[p.o:])
82 if len(p.contexts) > 0 {
83 remaining += ", context " + strings.Join(p.contexts, ",")
84 }
85 remaining = " (" + remaining + ")"
86 if p.conn.account != nil {
87 errmsg += remaining
88 err = errors.New(errmsg)
89 } else {
90 err = errors.New(errmsg + remaining)
91 }
92 panic(syntaxError{"", "", errmsg, err})
93}
94
95func (p *parser) context(s string) func() {
96 p.contexts = append(p.contexts, s)
97 return func() {
98 p.contexts = p.contexts[:len(p.contexts)-1]
99 }
100}
101
102func (p *parser) empty() bool {
103 return p.o == len(p.upper)
104}
105
106func (p *parser) xempty() {
107 if !p.empty() {
108 p.xerrorf("leftover data")
109 }
110}
111
112func (p *parser) hasPrefix(s string) bool {
113 return strings.HasPrefix(p.upper[p.o:], s)
114}
115
116func (p *parser) take(s string) bool {
117 if !p.hasPrefix(s) {
118 return false
119 }
120 p.o += len(s)
121 return true
122}
123
124func (p *parser) xtake(s string) {
125 if !p.take(s) {
126 p.xerrorf("expected %s", s)
127 }
128}
129
130func (p *parser) xnonempty() {
131 if p.empty() {
132 p.xerrorf("unexpected end")
133 }
134}
135
136func (p *parser) xtakeall() string {
137 r := p.orig[p.o:]
138 p.o = len(p.orig)
139 return r
140}
141
142func (p *parser) xtake1n(n int, what string) string {
143 if n == 0 {
144 p.xerrorf("expected chars from %s", what)
145 }
146 return p.xtaken(n)
147}
148
149func (p *parser) xtakechars(s string, what string) string {
150 p.xnonempty()
151 for i, c := range p.orig[p.o:] {
152 if !contains(s, c) {
153 return p.xtake1n(i, what)
154 }
155 }
156 return p.xtakeall()
157}
158
159func (p *parser) xtaken(n int) string {
160 if p.o+n > len(p.orig) {
161 p.xerrorf("not enough data")
162 }
163 r := p.orig[p.o : p.o+n]
164 p.o += n
165 return r
166}
167
168func (p *parser) space() bool {
169 return p.take(" ")
170}
171
172func (p *parser) xspace() {
173 if !p.space() {
174 p.xerrorf("expected space")
175 }
176}
177
178func (p *parser) digits() string {
179 var n int
180 for _, c := range p.upper[p.o:] {
181 if c < '0' || c > '9' {
182 break
183 }
184 n++
185 }
186 if n == 0 {
187 return ""
188 }
189 s := p.upper[p.o : p.o+n]
190 p.o += n
191 return s
192}
193
194func (p *parser) nznumber() (uint32, bool) {
195 o := p.o
196 for o < len(p.upper) && p.upper[o] >= '0' && p.upper[o] <= '9' {
197 o++
198 }
199 if o == p.o {
200 return 0, false
201 }
202 if n, err := strconv.ParseUint(p.upper[p.o:o], 10, 32); err != nil {
203 return 0, false
204 } else if n == 0 {
205 return 0, false
206 } else {
207 p.o = o
208 return uint32(n), true
209 }
210}
211
212func (p *parser) xnznumber() uint32 {
213 n, ok := p.nznumber()
214 if !ok {
215 p.xerrorf("expected non-zero number")
216 }
217 return n
218}
219
220func (p *parser) number() (uint32, bool) {
221 o := p.o
222 for o < len(p.upper) && p.upper[o] >= '0' && p.upper[o] <= '9' {
223 o++
224 }
225 if o == p.o {
226 return 0, false
227 }
228 n, err := strconv.ParseUint(p.upper[p.o:o], 10, 32)
229 if err != nil {
230 return 0, false
231 }
232 p.o = o
233 return uint32(n), true
234}
235
236func (p *parser) xnumber() uint32 {
237 n, ok := p.number()
238 if !ok {
239 p.xerrorf("expected number")
240 }
241 return n
242}
243
244func (p *parser) xnumber64() int64 {
245 s := p.digits()
246 if s == "" {
247 p.xerrorf("expected number64")
248 }
249 v, err := strconv.ParseInt(s, 10, 63) // ../rfc/9051:6794 ../rfc/7162:297
250 if err != nil {
251 p.xerrorf("parsing number64 %q: %v", s, err)
252 }
253 return v
254}
255
256func (p *parser) xnznumber64() int64 {
257 v := p.xnumber64()
258 if v == 0 {
259 p.xerrorf("expected non-zero number64")
260 }
261 return v
262}
263
264// l should be a list of uppercase words, the first match is returned
265func (p *parser) takelist(l ...string) (string, bool) {
266 for _, w := range l {
267 if p.take(w) {
268 return w, true
269 }
270 }
271 return "", false
272}
273
274func (p *parser) xtakelist(l ...string) string {
275 w, ok := p.takelist(l...)
276 if !ok {
277 p.xerrorf("expected one of %s", strings.Join(l, ","))
278 }
279 return w
280}
281
282func (p *parser) xstring() (r string) {
283 if p.take(`"`) {
284 esc := false
285 r := ""
286 for i, c := range p.orig[p.o:] {
287 if c == '\\' {
288 esc = true
289 } else if c == '\x00' || c == '\r' || c == '\n' {
290 p.xerrorf("invalid nul, cr or lf in string")
291 } else if esc {
292 if c == '\\' || c == '"' {
293 r += string(c)
294 esc = false
295 } else {
296 p.xerrorf("invalid escape char %c", c)
297 }
298 } else if c == '"' {
299 p.o += i + 1
300 return r
301 } else {
302 r += string(c)
303 }
304 }
305 p.xerrorf("missing closing dquote in string")
306 }
307 size, sync := p.xliteralSize(false, true)
308 s := p.conn.xreadliteral(size, sync)
309 line := p.conn.readline(false)
310 p.orig, p.upper, p.o = line, toUpper(line), 0
311 return s
312}
313
314func (p *parser) xnil() {
315 p.xtake("NIL")
316}
317
318// Returns NIL as empty string.
319func (p *parser) xnilString() string {
320 if p.take("NIL") {
321 return ""
322 }
323 return p.xstring()
324}
325
326func (p *parser) xastring() string {
327 if p.hasPrefix(`"`) || p.hasPrefix("{") || p.hasPrefix("~{") {
328 return p.xstring()
329 }
330 return p.xtakechars(astringChar, "astring")
331}
332
333func contains(s string, c rune) bool {
334 for _, x := range s {
335 if x == c {
336 return true
337 }
338 }
339 return false
340}
341
342func (p *parser) xtag() string {
343 p.xnonempty()
344 for i, c := range p.orig[p.o:] {
345 if c == '+' || !contains(astringChar, c) {
346 return p.xtake1n(i, "tag")
347 }
348 }
349 return p.xtakeall()
350}
351
352func (p *parser) xcommand() string {
353 for i, c := range p.upper[p.o:] {
354 if !(c >= 'A' && c <= 'Z' || c == ' ' && p.upper[p.o:p.o+i] == "UID") {
355 return p.xtake1n(i, "command")
356 }
357 }
358 return p.xtakeall()
359}
360
361func (p *parser) remainder() string {
362 return p.orig[p.o:]
363}
364
365// ../rfc/9051:6565
366func (p *parser) xflag() string {
367 w, _ := p.takelist(`\`, "$")
368 s := w + p.xatom()
369 if s[0] == '\\' {
370 switch strings.ToLower(s) {
371 case `\answered`, `\flagged`, `\deleted`, `\seen`, `\draft`:
372 default:
373 p.xerrorf("unknown system flag %s", s)
374 }
375 }
376 return s
377}
378
379func (p *parser) xflagList() (l []string) {
380 p.xtake("(")
381 if !p.hasPrefix(")") {
382 l = append(l, p.xflag())
383 }
384 for !p.take(")") {
385 p.xspace()
386 l = append(l, p.xflag())
387 }
388 return
389}
390
391func (p *parser) xatom() string {
392 return p.xtakechars(atomChar, "atom")
393}
394
395func (p *parser) xdecodeMailbox(s string) string {
396 // UTF-7 is deprecated for IMAP4rev2-only clients, and not used with UTF8=ACCEPT.
397 // The future should be without UTF-7, we don't encode/decode it with modern
398 // clients. Most clients are IMAP4rev1, we need to handle UTF-7.
399 // ../rfc/3501:964 ../rfc/9051:7885
400 // Thunderbird will enable UTF8=ACCEPT and send "&" unencoded. ../rfc/9051:7953
401 if p.conn.utf8strings() {
402 return s
403 }
404 ns, err := utf7decode(s)
405 if err != nil {
406 p.xerrorf("decoding utf7 mailbox name: %v", err)
407 }
408 return ns
409}
410
411func (p *parser) xmailbox() string {
412 s := p.xastring()
413 return p.xdecodeMailbox(s)
414}
415
416// ../rfc/9051:6605
417func (p *parser) xlistMailbox() string {
418 var s string
419 if p.hasPrefix(`"`) || p.hasPrefix("{") {
420 s = p.xstring()
421 } else {
422 s = p.xtakechars(atomChar+listWildcards+respSpecials, "list-char")
423 }
424 // Presumably UTF-7 encoding applies to mailbox patterns too.
425 return p.xdecodeMailbox(s)
426}
427
428// ../rfc/9051:6707 ../rfc/9051:6848 ../rfc/5258:1095 ../rfc/5258:1169 ../rfc/5258:1196
429func (p *parser) xmboxOrPat() ([]string, bool) {
430 if !p.take("(") {
431 return []string{p.xlistMailbox()}, false
432 }
433 l := []string{p.xlistMailbox()}
434 for !p.take(")") {
435 p.xspace()
436 l = append(l, p.xlistMailbox())
437 }
438 return l, true
439}
440
441// ../rfc/9051:7056, RECENT ../rfc/3501:5047, APPENDLIMIT ../rfc/7889:252, HIGHESTMODSEQ ../rfc/7162:2452, DELETED-STORAGE ../rfc/9208:696
442func (p *parser) xstatusAtt() string {
443 w := p.xtakelist("MESSAGES", "UIDNEXT", "UIDVALIDITY", "UNSEEN", "DELETED-STORAGE", "DELETED", "SIZE", "RECENT", "APPENDLIMIT", "HIGHESTMODSEQ")
444 if w == "HIGHESTMODSEQ" {
445 // HIGHESTMODSEQ is a CONDSTORE-enabling parameter. ../rfc/7162:375
446 p.conn.enabled[capCondstore] = true
447 }
448 return w
449}
450
451// ../rfc/9051:7133 ../rfc/9051:7034
452func (p *parser) xnumSet0(allowStar, allowSearch bool) (r numSet) {
453 defer p.context("numSet")()
454 if allowSearch && p.take("$") {
455 return numSet{searchResult: true}
456 }
457 r.ranges = append(r.ranges, p.xnumRange0(allowStar))
458 for p.take(",") {
459 r.ranges = append(r.ranges, p.xnumRange0(allowStar))
460 }
461 return r
462}
463
464func (p *parser) xnumSet() (r numSet) {
465 return p.xnumSet0(true, true)
466}
467
468// parse numRange, which can be just a setNumber.
469func (p *parser) xnumRange0(allowStar bool) (r numRange) {
470 if allowStar && p.take("*") {
471 r.first.star = true
472 } else {
473 r.first.number = p.xnznumber()
474 }
475 if p.take(":") {
476 r.last = &setNumber{}
477 if allowStar && p.take("*") {
478 r.last.star = true
479 } else {
480 r.last.number = p.xnznumber()
481 }
482 }
483 return
484}
485
486// ../rfc/9051:6989 ../rfc/3501:4977
487func (p *parser) xsectionMsgtext() (r *sectionMsgtext) {
488 defer p.context("sectionMsgtext")()
489 msgtextWords := []string{"HEADER.FIELDS.NOT", "HEADER.FIELDS", "HEADER", "TEXT"}
490 w := p.xtakelist(msgtextWords...)
491 r = &sectionMsgtext{s: w}
492 if strings.HasPrefix(w, "HEADER.FIELDS") {
493 p.xspace()
494 p.xtake("(")
495 r.headers = append(r.headers, textproto.CanonicalMIMEHeaderKey(p.xastring()))
496 for {
497 if p.take(")") {
498 break
499 }
500 p.xspace()
501 r.headers = append(r.headers, textproto.CanonicalMIMEHeaderKey(p.xastring()))
502 }
503 }
504 return
505}
506
507// ../rfc/9051:6999 ../rfc/3501:4991
508func (p *parser) xsectionSpec() (r *sectionSpec) {
509 defer p.context("parseSectionSpec")()
510
511 n, ok := p.nznumber()
512 if !ok {
513 return &sectionSpec{msgtext: p.xsectionMsgtext()}
514 }
515 defer p.context("part...")()
516 pt := &sectionPart{}
517 pt.part = append(pt.part, n)
518 for {
519 if !p.take(".") {
520 break
521 }
522 if n, ok := p.nznumber(); ok {
523 pt.part = append(pt.part, n)
524 continue
525 }
526 if p.take("MIME") {
527 pt.text = &sectionText{mime: true}
528 break
529 }
530 pt.text = &sectionText{msgtext: p.xsectionMsgtext()}
531 break
532 }
533 return &sectionSpec{part: pt}
534}
535
536// ../rfc/9051:6985 ../rfc/3501:4975
537func (p *parser) xsection() *sectionSpec {
538 defer p.context("parseSection")()
539 p.xtake("[")
540 if p.take("]") {
541 return &sectionSpec{}
542 }
543 r := p.xsectionSpec()
544 p.xtake("]")
545 return r
546}
547
548// ../rfc/9051:6841
549func (p *parser) xpartial() *partial {
550 p.xtake("<")
551 offset := p.xnumber()
552 p.xtake(".")
553 count := p.xnznumber()
554 p.xtake(">")
555 return &partial{offset, count}
556}
557
558// ../rfc/9051:6987
559func (p *parser) xsectionBinary() (r []uint32) {
560 p.xtake("[")
561 if p.take("]") {
562 return nil
563 }
564 r = append(r, p.xnznumber())
565 for {
566 if !p.take(".") {
567 break
568 }
569 r = append(r, p.xnznumber())
570 }
571 p.xtake("]")
572 return r
573}
574
575var fetchAttWords = []string{
576 "ENVELOPE", "FLAGS", "INTERNALDATE", "RFC822.SIZE", "BODYSTRUCTURE", "UID", "BODY.PEEK", "BODY", "BINARY.PEEK", "BINARY.SIZE", "BINARY",
577 "RFC822.HEADER", "RFC822.TEXT", "RFC822", // older IMAP
578 "MODSEQ", // CONDSTORE extension.
579}
580
581// ../rfc/9051:6557 ../rfc/3501:4751 ../rfc/7162:2483
582func (p *parser) xfetchAtt(isUID bool) (r fetchAtt) {
583 defer p.context("fetchAtt")()
584 f := p.xtakelist(fetchAttWords...)
585 r.peek = strings.HasSuffix(f, ".PEEK")
586 r.field = strings.TrimSuffix(f, ".PEEK")
587
588 switch r.field {
589 case "BODY":
590 if p.hasPrefix("[") {
591 r.section = p.xsection()
592 if p.hasPrefix("<") {
593 r.partial = p.xpartial()
594 }
595 }
596 case "BINARY":
597 r.sectionBinary = p.xsectionBinary()
598 if p.hasPrefix("<") {
599 r.partial = p.xpartial()
600 }
601 case "BINARY.SIZE":
602 r.sectionBinary = p.xsectionBinary()
603 case "MODSEQ":
604 // The RFC text mentions MODSEQ is only for FETCH, not UID FETCH, but the ABNF adds
605 // the attribute to the shared syntax, so UID FETCH also implements it.
606 // ../rfc/7162:905
607 // The wording about when to respond with a MODSEQ attribute could be more clear. ../rfc/7162:923 ../rfc/7162:388
608 // MODSEQ attribute is a CONDSTORE-enabling parameter. ../rfc/7162:377
609 p.conn.xensureCondstore(nil)
610 }
611 return
612}
613
614// ../rfc/9051:6553 ../rfc/3501:4748
615func (p *parser) xfetchAtts(isUID bool) []fetchAtt {
616 defer p.context("fetchAtts")()
617
618 fields := func(l ...string) []fetchAtt {
619 r := make([]fetchAtt, len(l))
620 for i, s := range l {
621 r[i] = fetchAtt{field: s}
622 }
623 return r
624 }
625
626 if w, ok := p.takelist("ALL", "FAST", "FULL"); ok {
627 switch w {
628 case "ALL":
629 return fields("FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE")
630 case "FAST":
631 return fields("FLAGS", "INTERNALDATE", "RFC822.SIZE")
632 case "FULL":
633 return fields("FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODY")
634 }
635 panic("missing case")
636 }
637
638 if !p.hasPrefix("(") {
639 return []fetchAtt{p.xfetchAtt(isUID)}
640 }
641
642 l := []fetchAtt{}
643 p.xtake("(")
644 for {
645 l = append(l, p.xfetchAtt(isUID))
646 if !p.take(" ") {
647 break
648 }
649 }
650 p.xtake(")")
651 return l
652}
653
654func xint(p *parser, s string) int {
655 v, err := strconv.ParseInt(s, 10, 32)
656 if err != nil {
657 p.xerrorf("bad int %q: %v", s, err)
658 }
659 return int(v)
660}
661
662func (p *parser) digit() (string, bool) {
663 if p.empty() {
664 return "", false
665 }
666 c := p.orig[p.o]
667 if c < '0' || c > '9' {
668 return "", false
669 }
670 s := p.orig[p.o : p.o+1]
671 p.o++
672 return s, true
673}
674
675func (p *parser) xdigit() string {
676 s, ok := p.digit()
677 if !ok {
678 p.xerrorf("expected digit")
679 }
680 return s
681}
682
683// ../rfc/9051:6492 ../rfc/3501:4695
684func (p *parser) xdateDayFixed() int {
685 if p.take(" ") {
686 return xint(p, p.xdigit())
687 }
688 return xint(p, p.xdigit()+p.xdigit())
689}
690
691var months = []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}
692
693// ../rfc/9051:6495 ../rfc/3501:4698
694func (p *parser) xdateMonth() time.Month {
695 s := strings.ToLower(p.xtaken(3))
696 for i, m := range months {
697 if m == s {
698 return time.Month(1 + i)
699 }
700 }
701 p.xerrorf("unknown month %q", s)
702 return 0
703}
704
705// ../rfc/9051:7120 ../rfc/3501:5067
706func (p *parser) xtime() (int, int, int) {
707 h := xint(p, p.xtaken(2))
708 p.xtake(":")
709 m := xint(p, p.xtaken(2))
710 p.xtake(":")
711 s := xint(p, p.xtaken(2))
712 return h, m, s
713}
714
715// ../rfc/9051:7159 ../rfc/3501:5083
716func (p *parser) xzone() (string, int) {
717 sign := p.xtakelist("+", "-")
718 s := p.xtaken(4)
719 v := xint(p, s)
720 seconds := (v/100)*3600 + (v%100)*60
721 if sign[0] == '-' {
722 seconds = -seconds
723 }
724 return sign + s, seconds
725}
726
727// ../rfc/9051:6502 ../rfc/3501:4713
728func (p *parser) xdateTime() time.Time {
729 // DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE
730 p.xtake(`"`)
731 day := p.xdateDayFixed()
732 p.xtake("-")
733 month := p.xdateMonth()
734 p.xtake("-")
735 year := xint(p, p.xtaken(4))
736 p.xspace()
737 hours, minutes, seconds := p.xtime()
738 p.xspace()
739 name, zoneSeconds := p.xzone()
740 p.xtake(`"`)
741 loc := time.FixedZone(name, zoneSeconds)
742 return time.Date(year, month, day, hours, minutes, seconds, 0, loc)
743}
744
745// ../rfc/9051:6655 ../rfc/7888:330 ../rfc/3501:4801
746func (p *parser) xliteralSize(lit8 bool, checkSize bool) (size int64, sync bool) {
747 // todo: enforce that we get non-binary when ~ isn't present?
748 if lit8 {
749 p.take("~")
750 }
751 p.xtake("{")
752 size = p.xnumber64()
753
754 sync = !p.take("+")
755 p.xtake("}")
756 p.xempty()
757
758 if checkSize {
759 // ../rfc/7888:249
760 var errmsg string
761 const (
762 litSizeMax = 100 * 1024
763 totalLitSizeMax = 10 * litSizeMax
764 litMax = 1000
765 )
766 p.literalSize += size
767 p.literals++
768 if size > litSizeMax {
769 errmsg = fmt.Sprintf("max literal size %d is larger than allowed %d", size, litSizeMax)
770 } else if p.literalSize > totalLitSizeMax {
771 errmsg = fmt.Sprintf("max total literal size for command %d is larger than allowed %d", p.literalSize, totalLitSizeMax)
772 } else if p.literals > litMax {
773 errmsg = fmt.Sprintf("max literals for command %d is larger than allowed %d", p.literals, litMax)
774 }
775 if errmsg != "" {
776 // ../rfc/9051:357 ../rfc/3501:347
777 err := errors.New("literal too big: " + errmsg)
778 if sync {
779 errmsg = ""
780 } else {
781 errmsg = "* BYE [ALERT] " + errmsg
782 }
783 panic(syntaxError{errmsg, "TOOBIG", err.Error(), err})
784 }
785 }
786
787 return size, sync
788}
789
790var searchKeyWords = []string{
791 "ALL", "ANSWERED", "BCC",
792 "BEFORE", "BODY",
793 "CC", "DELETED", "FLAGGED",
794 "FROM", "KEYWORD",
795 "NEW", "OLD", "ON", "RECENT", "SEEN",
796 "SINCE", "SUBJECT",
797 "TEXT", "TO",
798 "UNANSWERED", "UNDELETED", "UNFLAGGED",
799 "UNKEYWORD", "UNSEEN",
800 "DRAFT", "HEADER",
801 "LARGER", "NOT",
802 "OR",
803 "SENTBEFORE", "SENTON",
804 "SENTSINCE", "SMALLER",
805 "UID", "UNDRAFT",
806 "MODSEQ", // CONDSTORE extension.
807}
808
809// ../rfc/9051:6923 ../rfc/3501:4957, MODSEQ ../rfc/7162:2492
810// differences: rfc 9051 removes NEW, OLD, RECENT and makes SMALLER and LARGER number64 instead of number.
811func (p *parser) xsearchKey() *searchKey {
812 if p.take("(") {
813 sk := p.xsearchKey()
814 l := []searchKey{*sk}
815 for !p.take(")") {
816 p.xspace()
817 l = append(l, *p.xsearchKey())
818 }
819 return &searchKey{searchKeys: l}
820 }
821
822 w, ok := p.takelist(searchKeyWords...)
823 if !ok {
824 seqs := p.xnumSet()
825 return &searchKey{seqSet: &seqs}
826 }
827
828 sk := &searchKey{op: w}
829 switch sk.op {
830 case "ALL":
831 case "ANSWERED":
832 case "BCC":
833 p.xspace()
834 sk.astring = p.xastring()
835 case "BEFORE":
836 p.xspace()
837 sk.date = p.xdate()
838 case "BODY":
839 p.xspace()
840 sk.astring = p.xastring()
841 case "CC":
842 p.xspace()
843 sk.astring = p.xastring()
844 case "DELETED":
845 case "FLAGGED":
846 case "FROM":
847 p.xspace()
848 sk.astring = p.xastring()
849 case "KEYWORD":
850 p.xspace()
851 sk.atom = p.xatom()
852 case "NEW":
853 case "OLD":
854 case "ON":
855 p.xspace()
856 sk.date = p.xdate()
857 case "RECENT":
858 case "SEEN":
859 case "SINCE":
860 p.xspace()
861 sk.date = p.xdate()
862 case "SUBJECT":
863 p.xspace()
864 sk.astring = p.xastring()
865 case "TEXT":
866 p.xspace()
867 sk.astring = p.xastring()
868 case "TO":
869 p.xspace()
870 sk.astring = p.xastring()
871 case "UNANSWERED":
872 case "UNDELETED":
873 case "UNFLAGGED":
874 case "UNKEYWORD":
875 p.xspace()
876 sk.atom = p.xatom()
877 case "UNSEEN":
878 case "DRAFT":
879 case "HEADER":
880 p.xspace()
881 sk.headerField = p.xastring()
882 p.xspace()
883 sk.astring = p.xastring()
884 case "LARGER":
885 p.xspace()
886 sk.number = p.xnumber64()
887 case "NOT":
888 p.xspace()
889 sk.searchKey = p.xsearchKey()
890 case "OR":
891 p.xspace()
892 sk.searchKey = p.xsearchKey()
893 p.xspace()
894 sk.searchKey2 = p.xsearchKey()
895 case "SENTBEFORE":
896 p.xspace()
897 sk.date = p.xdate()
898 case "SENTON":
899 p.xspace()
900 sk.date = p.xdate()
901 case "SENTSINCE":
902 p.xspace()
903 sk.date = p.xdate()
904 case "SMALLER":
905 p.xspace()
906 sk.number = p.xnumber64()
907 case "UID":
908 p.xspace()
909 sk.uidSet = p.xnumSet()
910 case "UNDRAFT":
911 case "MODSEQ":
912 // ../rfc/7162:1045 ../rfc/7162:2499
913 p.xspace()
914 if p.take(`"`) {
915 // We don't do anything with this field, so parse and ignore.
916 p.xtake(`/FLAGS/`)
917 if p.take(`\`) {
918 p.xtake(`\`) // ../rfc/7162:1072
919 }
920 p.xatom()
921 p.xtake(`"`)
922 p.xspace()
923 p.xtakelist("PRIV", "SHARED", "ALL")
924 p.xspace()
925 }
926 v := p.xnumber64()
927 sk.clientModseq = &v
928 // MODSEQ is a CONDSTORE-enabling parameter. ../rfc/7162:377
929 p.conn.enabled[capCondstore] = true
930 default:
931 p.xerrorf("missing case for op %q", sk.op)
932 }
933 return sk
934}
935
936// hasModseq returns whether there is a modseq filter anywhere in the searchkey.
937func (sk searchKey) hasModseq() bool {
938 if sk.clientModseq != nil {
939 return true
940 }
941 for _, e := range sk.searchKeys {
942 if e.hasModseq() {
943 return true
944 }
945 }
946 if sk.searchKey != nil && sk.searchKey.hasModseq() {
947 return true
948 }
949 if sk.searchKey2 != nil && sk.searchKey2.hasModseq() {
950 return true
951 }
952 return false
953}
954
955// ../rfc/9051:6489 ../rfc/3501:4692
956func (p *parser) xdateDay() int {
957 d := p.xdigit()
958 if s, ok := p.digit(); ok {
959 d += s
960 }
961 return xint(p, d)
962}
963
964// ../rfc/9051:6487 ../rfc/3501:4690
965func (p *parser) xdate() time.Time {
966 dquote := p.take(`"`)
967 day := p.xdateDay()
968 p.xtake("-")
969 mon := p.xdateMonth()
970 p.xtake("-")
971 year := xint(p, p.xtaken(4))
972 if dquote {
973 p.take(`"`)
974 }
975 return time.Date(year, mon, day, 0, 0, 0, 0, time.UTC)
976}
977