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