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.xreadline(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() (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() []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()}
644 }
645
646 l := []fetchAtt{}
647 p.xtake("(")
648 for {
649 l = append(l, p.xfetchAtt())
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// ../rfc/9051:6489 ../rfc/3501:4692
950func (p *parser) xdateDay() int {
951 d := p.xdigit()
952 if s, ok := p.digit(); ok {
953 d += s
954 }
955 return xint(p, d)
956}
957
958// ../rfc/9051:6487 ../rfc/3501:4690
959func (p *parser) xdate() time.Time {
960 dquote := p.take(`"`)
961 day := p.xdateDay()
962 p.xtake("-")
963 mon := p.xdateMonth()
964 p.xtake("-")
965 year := xint(p, p.xtaken(4))
966 if dquote {
967 p.take(`"`)
968 }
969 return time.Date(year, mon, day, 0, 0, 0, 0, time.UTC)
970}
971
972// Parse and validate a metadata key (entry name), returned as lower-case.
973//
974// ../rfc/5464:190
975func (p *parser) xmetadataKey() string {
976 // ../rfc/5464:772
977 s := p.xastring()
978
979 // ../rfc/5464:192
980 if strings.Contains(s, "//") {
981 p.xerrorf("entry name must not contain two slashes")
982 }
983 // We allow a single slash, so it can be used with option "(depth infinity)" to get
984 // all annotations.
985 if s != "/" && strings.HasSuffix(s, "/") {
986 p.xerrorf("entry name must not end with slash")
987 }
988 // ../rfc/5464:202
989 if strings.Contains(s, "*") || strings.Contains(s, "%") {
990 p.xerrorf("entry name must not contain * or %%")
991 }
992 for _, c := range s {
993 if c < ' ' || c >= 0x7f {
994 p.xerrorf("entry name must only contain non-control ascii characters")
995 }
996 }
997 return strings.ToLower(s)
998}
999
1000// ../rfc/5464:776
1001func (p *parser) xmetadataKeyValue() (key string, isString bool, value []byte) {
1002 key = p.xmetadataKey()
1003 p.xspace()
1004
1005 if p.hasPrefix("~{") {
1006 size, sync := p.xliteralSize(true, true)
1007 value = p.conn.xreadliteral(size, sync)
1008 line := p.conn.xreadline(false)
1009 p.orig, p.upper, p.o = line, toUpper(line), 0
1010 } else if p.hasPrefix(`"`) {
1011 value = []byte(p.xstring())
1012 isString = true
1013 } else if p.take("NIL") {
1014 value = nil
1015 } else {
1016 p.xerrorf("expected metadata value")
1017 }
1018
1019 return
1020}
1021
1022type eventGroup struct {
1023 MailboxSpecifier mailboxSpecifier
1024 Events []notifyEvent // NONE is represented by an empty list.
1025}
1026
1027type mbspecKind string
1028
1029const (
1030 mbspecSelected mbspecKind = "SELECTED"
1031 mbspecSelectedDelayed mbspecKind = "SELECTED-DELAYED" // Only for NOTIFY.
1032 mbspecInboxes mbspecKind = "INBOXES"
1033 mbspecPersonal mbspecKind = "PERSONAL"
1034 mbspecSubscribed mbspecKind = "SUBSCRIBED"
1035 mbspecSubtreeOne mbspecKind = "SUBTREE-ONE" // For ESEARCH, we allow it for NOTIFY too.
1036 mbspecSubtree mbspecKind = "SUBTREE"
1037 mbspecMailboxes mbspecKind = "MAILBOXES"
1038)
1039
1040// Used by both the ESEARCH and NOTIFY commands.
1041type mailboxSpecifier struct {
1042 Kind mbspecKind
1043 Mailboxes []string
1044}
1045
1046type notifyEvent struct {
1047 // Kind is always upper case. Should be one of eventKind, anything else must result
1048 // in a BADEVENT response code.
1049 Kind string
1050
1051 FetchAtt []fetchAtt // Only for MessageNew
1052}
1053
1054// ../rfc/5465:943
1055func (p *parser) xeventGroup() (eg eventGroup) {
1056 p.xtake("(")
1057 eg.MailboxSpecifier = p.xfilterMailbox(mbspecsNotify)
1058 p.xspace()
1059 if p.take("NONE") {
1060 p.xtake(")")
1061 return eg
1062 }
1063 p.xtake("(")
1064 for {
1065 e := p.xnotifyEvent()
1066 eg.Events = append(eg.Events, e)
1067 if !p.space() {
1068 break
1069 }
1070 }
1071 p.xtake(")")
1072 p.xtake(")")
1073 return eg
1074}
1075
1076var mbspecsEsearch = []mbspecKind{
1077 mbspecSelected, // selected-delayed is only for NOTIFY.
1078 mbspecInboxes,
1079 mbspecPersonal,
1080 mbspecSubscribed,
1081 mbspecSubtreeOne, // Must come before Subtree due to eager parsing.
1082 mbspecSubtree,
1083 mbspecMailboxes,
1084}
1085
1086var mbspecsNotify = []mbspecKind{
1087 mbspecSelectedDelayed, // Must come before mbspecSelected, for eager parsing and mbspecSelected.
1088 mbspecSelected,
1089 mbspecInboxes,
1090 mbspecPersonal,
1091 mbspecSubscribed,
1092 mbspecSubtreeOne, // From ESEARCH, we also allow it in NOTIFY.
1093 mbspecSubtree,
1094 mbspecMailboxes,
1095}
1096
1097// If not esearch with "subtree-one", then for notify with "selected-delayed".
1098func (p *parser) xfilterMailbox(allowed []mbspecKind) (ms mailboxSpecifier) {
1099 var kind mbspecKind
1100 for _, s := range allowed {
1101 if p.take(string(s)) {
1102 kind = s
1103 break
1104 }
1105 }
1106 if kind == mbspecKind("") {
1107 xsyntaxErrorf("expected mailbox specifier")
1108 }
1109
1110 ms.Kind = kind
1111 switch kind {
1112 case "SUBTREE", "SUBTREE-ONE", "MAILBOXES":
1113 p.xtake(" ")
1114 // One or more mailboxes. Multiple start with a list. ../rfc/5465:937
1115 if p.take("(") {
1116 for {
1117 ms.Mailboxes = append(ms.Mailboxes, p.xmailbox())
1118 if !p.take(" ") {
1119 break
1120 }
1121 }
1122 p.xtake(")")
1123 } else {
1124 ms.Mailboxes = []string{p.xmailbox()}
1125 }
1126 }
1127 return ms
1128}
1129
1130type eventKind string
1131
1132const (
1133 eventMessageNew eventKind = "MESSAGENEW"
1134 eventMessageExpunge eventKind = "MESSAGEEXPUNGE"
1135 eventFlagChange eventKind = "FLAGCHANGE"
1136 eventAnnotationChange eventKind = "ANNOTATIONCHANGE"
1137 eventMailboxName eventKind = "MAILBOXNAME"
1138 eventSubscriptionChange eventKind = "SUBSCRIPTIONCHANGE"
1139 eventMailboxMetadataChange eventKind = "MAILBOXMETADATACHANGE"
1140 eventServerMetadataChange eventKind = "SERVERMETADATACHANGE"
1141)
1142
1143var messageEventKinds = []eventKind{eventMessageNew, eventMessageExpunge, eventFlagChange, eventAnnotationChange}
1144
1145// ../rfc/5465:974
1146func (p *parser) xnotifyEvent() notifyEvent {
1147 s := strings.ToUpper(p.xatom())
1148 e := notifyEvent{Kind: s}
1149 if eventKind(e.Kind) == eventMessageNew {
1150 if p.take(" (") {
1151 for {
1152 a := p.xfetchAtt()
1153 e.FetchAtt = append(e.FetchAtt, a)
1154 if !p.take(" ") {
1155 break
1156 }
1157 }
1158 p.xtake(")")
1159 }
1160 }
1161 return e
1162}
1163