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 buf := p.conn.xreadliteral(size, sync)
309 line := p.conn.readline(false)
310 p.orig, p.upper, p.o = line, toUpper(line), 0
311 return string(buf)
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 "SAVEDATE", // SAVEDATE extension, ../rfc/8514:186
580 "PREVIEW", // ../rfc/8970:345
581}
582
583// ../rfc/9051:6557 ../rfc/3501:4751 ../rfc/7162:2483
584func (p *parser) xfetchAtt(isUID bool) (r fetchAtt) {
585 defer p.context("fetchAtt")()
586 f := p.xtakelist(fetchAttWords...)
587 r.peek = strings.HasSuffix(f, ".PEEK")
588 r.field = strings.TrimSuffix(f, ".PEEK")
589
590 switch r.field {
591 case "BODY":
592 if p.hasPrefix("[") {
593 r.section = p.xsection()
594 if p.hasPrefix("<") {
595 r.partial = p.xpartial()
596 }
597 }
598 case "BINARY":
599 r.sectionBinary = p.xsectionBinary()
600 if p.hasPrefix("<") {
601 r.partial = p.xpartial()
602 }
603 case "BINARY.SIZE":
604 r.sectionBinary = p.xsectionBinary()
605 case "MODSEQ":
606 // The RFC text mentions MODSEQ is only for FETCH, not UID FETCH, but the ABNF adds
607 // the attribute to the shared syntax, so UID FETCH also implements it.
608 // ../rfc/7162:905
609 // The wording about when to respond with a MODSEQ attribute could be more clear. ../rfc/7162:923 ../rfc/7162:388
610 // MODSEQ attribute is a CONDSTORE-enabling parameter. ../rfc/7162:377
611 p.conn.xensureCondstore(nil)
612 case "PREVIEW":
613 r.previewLazy = p.take(" (LAZY)")
614 }
615 return
616}
617
618// ../rfc/9051:6553 ../rfc/3501:4748
619func (p *parser) xfetchAtts(isUID bool) []fetchAtt {
620 defer p.context("fetchAtts")()
621
622 fields := func(l ...string) []fetchAtt {
623 r := make([]fetchAtt, len(l))
624 for i, s := range l {
625 r[i] = fetchAtt{field: s}
626 }
627 return r
628 }
629
630 if w, ok := p.takelist("ALL", "FAST", "FULL"); ok {
631 switch w {
632 case "ALL":
633 return fields("FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE")
634 case "FAST":
635 return fields("FLAGS", "INTERNALDATE", "RFC822.SIZE")
636 case "FULL":
637 return fields("FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODY")
638 }
639 panic("missing case")
640 }
641
642 if !p.hasPrefix("(") {
643 return []fetchAtt{p.xfetchAtt(isUID)}
644 }
645
646 l := []fetchAtt{}
647 p.xtake("(")
648 for {
649 l = append(l, p.xfetchAtt(isUID))
650 if !p.take(" ") {
651 break
652 }
653 }
654 p.xtake(")")
655 return l
656}
657
658func xint(p *parser, s string) int {
659 v, err := strconv.ParseInt(s, 10, 32)
660 if err != nil {
661 p.xerrorf("bad int %q: %v", s, err)
662 }
663 return int(v)
664}
665
666func (p *parser) digit() (string, bool) {
667 if p.empty() {
668 return "", false
669 }
670 c := p.orig[p.o]
671 if c < '0' || c > '9' {
672 return "", false
673 }
674 s := p.orig[p.o : p.o+1]
675 p.o++
676 return s, true
677}
678
679func (p *parser) xdigit() string {
680 s, ok := p.digit()
681 if !ok {
682 p.xerrorf("expected digit")
683 }
684 return s
685}
686
687// ../rfc/9051:6492 ../rfc/3501:4695
688func (p *parser) xdateDayFixed() int {
689 if p.take(" ") {
690 return xint(p, p.xdigit())
691 }
692 return xint(p, p.xdigit()+p.xdigit())
693}
694
695var months = []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}
696
697// ../rfc/9051:6495 ../rfc/3501:4698
698func (p *parser) xdateMonth() time.Month {
699 s := strings.ToLower(p.xtaken(3))
700 for i, m := range months {
701 if m == s {
702 return time.Month(1 + i)
703 }
704 }
705 p.xerrorf("unknown month %q", s)
706 return 0
707}
708
709// ../rfc/9051:7120 ../rfc/3501:5067
710func (p *parser) xtime() (int, int, int) {
711 h := xint(p, p.xtaken(2))
712 p.xtake(":")
713 m := xint(p, p.xtaken(2))
714 p.xtake(":")
715 s := xint(p, p.xtaken(2))
716 return h, m, s
717}
718
719// ../rfc/9051:7159 ../rfc/3501:5083
720func (p *parser) xzone() (string, int) {
721 sign := p.xtakelist("+", "-")
722 s := p.xtaken(4)
723 v := xint(p, s)
724 seconds := (v/100)*3600 + (v%100)*60
725 if sign[0] == '-' {
726 seconds = -seconds
727 }
728 return sign + s, seconds
729}
730
731// ../rfc/9051:6502 ../rfc/3501:4713
732func (p *parser) xdateTime() time.Time {
733 // DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE
734 p.xtake(`"`)
735 day := p.xdateDayFixed()
736 p.xtake("-")
737 month := p.xdateMonth()
738 p.xtake("-")
739 year := xint(p, p.xtaken(4))
740 p.xspace()
741 hours, minutes, seconds := p.xtime()
742 p.xspace()
743 name, zoneSeconds := p.xzone()
744 p.xtake(`"`)
745 loc := time.FixedZone(name, zoneSeconds)
746 return time.Date(year, month, day, hours, minutes, seconds, 0, loc)
747}
748
749// ../rfc/9051:6655 ../rfc/7888:330 ../rfc/3501:4801
750func (p *parser) xliteralSize(lit8 bool, checkSize bool) (size int64, sync bool) {
751 // todo: enforce that we get non-binary when ~ isn't present?
752 if lit8 {
753 p.take("~")
754 }
755 p.xtake("{")
756 size = p.xnumber64()
757
758 sync = !p.take("+")
759 p.xtake("}")
760 p.xempty()
761
762 if checkSize {
763 // ../rfc/7888:249
764 var errmsg string
765 const (
766 litSizeMax = 100 * 1024
767 totalLitSizeMax = 10 * litSizeMax
768 litMax = 1000
769 )
770 p.literalSize += size
771 p.literals++
772 if size > litSizeMax {
773 errmsg = fmt.Sprintf("max literal size %d is larger than allowed %d", size, litSizeMax)
774 } else if p.literalSize > totalLitSizeMax {
775 errmsg = fmt.Sprintf("max total literal size for command %d is larger than allowed %d", p.literalSize, totalLitSizeMax)
776 } else if p.literals > litMax {
777 errmsg = fmt.Sprintf("max literals for command %d is larger than allowed %d", p.literals, litMax)
778 }
779 if errmsg != "" {
780 // ../rfc/9051:357 ../rfc/3501:347
781 err := errors.New("literal too big: " + errmsg)
782 if sync {
783 errmsg = ""
784 } else {
785 errmsg = "* BYE [ALERT] " + errmsg
786 }
787 panic(syntaxError{errmsg, "TOOBIG", err.Error(), err})
788 }
789 }
790
791 return size, sync
792}
793
794var searchKeyWords = []string{
795 "ALL", "ANSWERED", "BCC",
796 "BEFORE", "BODY",
797 "CC", "DELETED", "FLAGGED",
798 "FROM", "KEYWORD",
799 "OLDER", "YOUNGER", // WITHIN extension, ../rfc/5032:72
800 "NEW", "OLD", "ON", "RECENT", "SEEN",
801 "SINCE", "SUBJECT",
802 "TEXT", "TO",
803 "UNANSWERED", "UNDELETED", "UNFLAGGED",
804 "UNKEYWORD", "UNSEEN",
805 "DRAFT", "HEADER",
806 "LARGER", "NOT",
807 "OR",
808 "SENTBEFORE", "SENTON",
809 "SENTSINCE", "SMALLER",
810 "UID", "UNDRAFT",
811 "MODSEQ", // CONDSTORE extension.
812 "SAVEDBEFORE", "SAVEDON", "SAVEDSINCE", "SAVEDATESUPPORTED", // SAVEDATE extension, ../rfc/8514:203
813}
814
815// ../rfc/9051:6923 ../rfc/3501:4957, MODSEQ ../rfc/7162:2492
816// differences: rfc 9051 removes NEW, OLD, RECENT and makes SMALLER and LARGER number64 instead of number.
817func (p *parser) xsearchKey() *searchKey {
818 if p.take("(") {
819 sk := p.xsearchKey()
820 l := []searchKey{*sk}
821 for !p.take(")") {
822 p.xspace()
823 l = append(l, *p.xsearchKey())
824 }
825 return &searchKey{searchKeys: l}
826 }
827
828 w, ok := p.takelist(searchKeyWords...)
829 if !ok {
830 seqs := p.xnumSet()
831 return &searchKey{seqSet: &seqs}
832 }
833
834 sk := &searchKey{op: w}
835 switch sk.op {
836 case "ALL":
837 case "ANSWERED":
838 case "BCC":
839 p.xspace()
840 sk.astring = p.xastring()
841 case "BEFORE":
842 p.xspace()
843 sk.date = p.xdate()
844 case "BODY":
845 p.xspace()
846 sk.astring = p.xastring()
847 case "CC":
848 p.xspace()
849 sk.astring = p.xastring()
850 case "DELETED":
851 case "FLAGGED":
852 case "FROM":
853 p.xspace()
854 sk.astring = p.xastring()
855 case "KEYWORD":
856 p.xspace()
857 sk.atom = p.xatom()
858 case "NEW":
859 case "OLD":
860 case "ON":
861 p.xspace()
862 sk.date = p.xdate()
863 case "RECENT":
864 case "SEEN":
865 case "SINCE":
866 p.xspace()
867 sk.date = p.xdate()
868 case "SUBJECT":
869 p.xspace()
870 sk.astring = p.xastring()
871 case "TEXT":
872 p.xspace()
873 sk.astring = p.xastring()
874 case "TO":
875 p.xspace()
876 sk.astring = p.xastring()
877 case "UNANSWERED":
878 case "UNDELETED":
879 case "UNFLAGGED":
880 case "UNKEYWORD":
881 p.xspace()
882 sk.atom = p.xatom()
883 case "UNSEEN":
884 case "DRAFT":
885 case "HEADER":
886 p.xspace()
887 sk.headerField = p.xastring()
888 p.xspace()
889 sk.astring = p.xastring()
890 case "LARGER":
891 p.xspace()
892 sk.number = p.xnumber64()
893 case "NOT":
894 p.xspace()
895 sk.searchKey = p.xsearchKey()
896 case "OR":
897 p.xspace()
898 sk.searchKey = p.xsearchKey()
899 p.xspace()
900 sk.searchKey2 = p.xsearchKey()
901 case "SENTBEFORE":
902 p.xspace()
903 sk.date = p.xdate()
904 case "SENTON":
905 p.xspace()
906 sk.date = p.xdate()
907 case "SENTSINCE":
908 p.xspace()
909 sk.date = p.xdate()
910 case "SMALLER":
911 p.xspace()
912 sk.number = p.xnumber64()
913 case "UID":
914 p.xspace()
915 sk.uidSet = p.xnumSet()
916 case "UNDRAFT":
917 case "MODSEQ":
918 // ../rfc/7162:1045 ../rfc/7162:2499
919 p.xspace()
920 if p.take(`"`) {
921 // We don't do anything with this field, so parse and ignore.
922 p.xtake(`/FLAGS/`)
923 if p.take(`\`) {
924 p.xtake(`\`) // ../rfc/7162:1072
925 }
926 p.xatom()
927 p.xtake(`"`)
928 p.xspace()
929 p.xtakelist("PRIV", "SHARED", "ALL")
930 p.xspace()
931 }
932 v := p.xnumber64()
933 sk.clientModseq = &v
934 // MODSEQ is a CONDSTORE-enabling parameter. ../rfc/7162:377
935 p.conn.enabled[capCondstore] = true
936 case "SAVEDBEFORE", "SAVEDON", "SAVEDSINCE":
937 p.xspace()
938 sk.date = p.xdate() // ../rfc/8514:267
939 case "SAVEDATESUPPORTED":
940 case "OLDER", "YOUNGER":
941 p.xspace()
942 sk.number = int64(p.xnznumber())
943 default:
944 p.xerrorf("missing case for op %q", sk.op)
945 }
946 return sk
947}
948
949// hasModseq returns whether there is a modseq filter anywhere in the searchkey.
950func (sk searchKey) hasModseq() bool {
951 if sk.clientModseq != nil {
952 return true
953 }
954 for _, e := range sk.searchKeys {
955 if e.hasModseq() {
956 return true
957 }
958 }
959 if sk.searchKey != nil && sk.searchKey.hasModseq() {
960 return true
961 }
962 if sk.searchKey2 != nil && sk.searchKey2.hasModseq() {
963 return true
964 }
965 return false
966}
967
968// Whether we need message sequence numbers to evaluate. If not, we cannot optimize
969// when only MAX is requested through a reverse query.
970func (sk searchKey) needSeq() bool {
971 for _, k := range sk.searchKeys {
972 if k.needSeq() {
973 return true
974 }
975 }
976 if sk.searchKey != nil && sk.searchKey.needSeq() {
977 return true
978 }
979 if sk.searchKey2 != nil && sk.searchKey2.needSeq() {
980 return true
981 }
982 return sk.seqSet != nil && !sk.seqSet.searchResult
983}
984
985// ../rfc/9051:6489 ../rfc/3501:4692
986func (p *parser) xdateDay() int {
987 d := p.xdigit()
988 if s, ok := p.digit(); ok {
989 d += s
990 }
991 return xint(p, d)
992}
993
994// ../rfc/9051:6487 ../rfc/3501:4690
995func (p *parser) xdate() time.Time {
996 dquote := p.take(`"`)
997 day := p.xdateDay()
998 p.xtake("-")
999 mon := p.xdateMonth()
1000 p.xtake("-")
1001 year := xint(p, p.xtaken(4))
1002 if dquote {
1003 p.take(`"`)
1004 }
1005 return time.Date(year, mon, day, 0, 0, 0, 0, time.UTC)
1006}
1007
1008// Parse and validate a metadata key (entry name), returned as lower-case.
1009//
1010// ../rfc/5464:190
1011func (p *parser) xmetadataKey() string {
1012 // ../rfc/5464:772
1013 s := p.xastring()
1014
1015 // ../rfc/5464:192
1016 if strings.Contains(s, "//") {
1017 p.xerrorf("entry name must not contain two slashes")
1018 }
1019 // We allow a single slash, so it can be used with option "(depth infinity)" to get
1020 // all annotations.
1021 if s != "/" && strings.HasSuffix(s, "/") {
1022 p.xerrorf("entry name must not end with slash")
1023 }
1024 // ../rfc/5464:202
1025 if strings.Contains(s, "*") || strings.Contains(s, "%") {
1026 p.xerrorf("entry name must not contain * or %%")
1027 }
1028 for _, c := range s {
1029 if c < ' ' || c >= 0x7f {
1030 p.xerrorf("entry name must only contain non-control ascii characters")
1031 }
1032 }
1033 return strings.ToLower(s)
1034}
1035
1036// ../rfc/5464:776
1037func (p *parser) xmetadataKeyValue() (key string, isString bool, value []byte) {
1038 key = p.xmetadataKey()
1039 p.xspace()
1040
1041 if p.hasPrefix("~{") {
1042 size, sync := p.xliteralSize(true, true)
1043 value = p.conn.xreadliteral(size, sync)
1044 line := p.conn.readline(false)
1045 p.orig, p.upper, p.o = line, toUpper(line), 0
1046 } else if p.hasPrefix(`"`) {
1047 value = []byte(p.xstring())
1048 isString = true
1049 } else if p.take("NIL") {
1050 value = nil
1051 } else {
1052 p.xerrorf("expected metadata value")
1053 }
1054
1055 return
1056}
1057