1package imapclient
2
3import (
4 "fmt"
5 "io"
6 "strconv"
7 "strings"
8 "time"
9
10 "github.com/mjl-/mox/mlog"
11)
12
13// todo: stricter parsing. xnonspace() and xword() should be replaced by proper parsers
14
15// Keep the parsing method names and the types similar to the ABNF names in the RFCs.
16
17func (p *Proto) recorded() string {
18 s := string(p.recordBuf)
19 p.recordBuf = nil
20 p.record = false
21 return s
22}
23
24func (p *Proto) recordAdd(buf []byte) {
25 if p.record {
26 p.recordBuf = append(p.recordBuf, buf...)
27 }
28}
29
30func (p *Proto) xtake(s string) {
31 buf := make([]byte, len(s))
32 _, err := io.ReadFull(p.br, buf)
33 p.xcheckf(err, "taking %q", s)
34 if !strings.EqualFold(string(buf), s) {
35 p.xerrorf("got %q, expected %q", buf, s)
36 }
37 p.recordAdd(buf)
38}
39
40func (p *Proto) readbyte() (byte, error) {
41 b, err := p.br.ReadByte()
42 if err == nil {
43 p.recordAdd([]byte{b})
44 }
45 return b, err
46}
47
48func (p *Proto) xunreadbyte() {
49 if p.record {
50 p.recordBuf = p.recordBuf[:len(p.recordBuf)-1]
51 }
52 err := p.br.UnreadByte()
53 p.xcheckf(err, "unread byte")
54}
55
56func (p *Proto) readrune() (rune, error) {
57 x, _, err := p.br.ReadRune()
58 if err == nil {
59 p.recordAdd([]byte(string(x)))
60 }
61 return x, err
62}
63
64func (p *Proto) space() bool {
65 return p.take(' ')
66}
67
68func (p *Proto) xspace() {
69 p.xtake(" ")
70}
71
72func (p *Proto) xcrlf() {
73 p.xtake("\r\n")
74}
75
76func (p *Proto) peek(exp byte) bool {
77 b, err := p.readbyte()
78 if err == nil {
79 p.xunreadbyte()
80 }
81 return err == nil && strings.EqualFold(string(rune(b)), string(rune(exp)))
82}
83
84func (p *Proto) peekstring() bool {
85 return p.peek('"') || p.peek('{')
86}
87
88func (p *Proto) take(exp byte) bool {
89 if p.peek(exp) {
90 _, _ = p.readbyte()
91 return true
92 }
93 return false
94}
95
96func (p *Proto) xstatus() Status {
97 w := p.xword()
98 W := strings.ToUpper(w)
99 switch W {
100 case "OK":
101 return OK
102 case "NO":
103 return NO
104 case "BAD":
105 return BAD
106 }
107 p.xerrorf("expected status, got %q", w)
108 panic("not reached")
109}
110
111// Already consumed: tag SP status SP
112func (p *Proto) xresult(status Status) Result {
113 code, text := p.xrespText()
114 return Result{status, code, text}
115}
116
117func (p *Proto) xrespText() (code Code, text string) {
118 if p.take('[') {
119 code = p.xrespCode()
120 p.xtake("]")
121 p.xspace()
122 }
123 for !p.peek('\r') {
124 text += string(rune(p.xbyte()))
125 }
126 return
127}
128
129// ../rfc/9051:6895
130func (p *Proto) xrespCode() Code {
131 w := ""
132 for !p.peek(' ') && !p.peek(']') {
133 w += string(rune(p.xbyte()))
134 }
135 W := strings.ToUpper(w)
136
137 switch W {
138 case "BADCHARSET":
139 var l []string // Must be nil initially.
140 if p.space() {
141 p.xtake("(")
142 l = []string{p.xcharset()}
143 for p.space() {
144 l = append(l, p.xcharset())
145 }
146 p.xtake(")")
147 }
148 return CodeBadCharset(l)
149 case "CAPABILITY":
150 p.xtake(" ")
151 caps := []Capability{}
152 for {
153 s := p.xatom()
154 s = strings.ToUpper(s)
155 caps = append(caps, Capability(s))
156 if !p.space() {
157 break
158 }
159 }
160 return CodeCapability(caps)
161 case "PERMANENTFLAGS":
162 l := []string{} // Must be non-nil.
163 if p.space() {
164 p.xtake("(")
165 l = []string{p.xflagPerm()}
166 for p.space() {
167 l = append(l, p.xflagPerm())
168 }
169 p.xtake(")")
170 }
171 return CodePermanentFlags(l)
172 case "UIDNEXT":
173 p.xspace()
174 return CodeUIDNext(p.xnzuint32())
175 case "UIDVALIDITY":
176 p.xspace()
177 return CodeUIDValidity(p.xnzuint32())
178 case "UNSEEN":
179 p.xspace()
180 return CodeUnseen(p.xnzuint32())
181 case "APPENDUID":
182 p.xspace()
183 destUIDValidity := p.xnzuint32()
184 p.xspace()
185 uids := p.xuidrange()
186 return CodeAppendUID{destUIDValidity, uids}
187 case "COPYUID":
188 p.xspace()
189 destUIDValidity := p.xnzuint32()
190 p.xspace()
191 from := p.xuidset()
192 p.xspace()
193 to := p.xuidset()
194 return CodeCopyUID{destUIDValidity, from, to}
195 case "HIGHESTMODSEQ":
196 p.xspace()
197 return CodeHighestModSeq(p.xint64())
198 case "MODIFIED":
199 p.xspace()
200 modified := p.xuidset()
201 return CodeModified(NumSet{Ranges: modified})
202 case "INPROGRESS":
203 // ../rfc/9585:238
204 var tag string
205 var current, goal *uint32
206 if p.space() {
207 p.xtake("(")
208 tag = p.xquoted()
209 p.xspace()
210 if p.peek('n') || p.peek('N') {
211 p.xtake("nil")
212 } else {
213 v := p.xuint32()
214 current = &v
215 }
216 p.xspace()
217 if p.peek('n') || p.peek('N') {
218 p.xtake("nil")
219 } else {
220 v := p.xnzuint32()
221 goal = &v
222 }
223 p.xtake(")")
224 }
225 return CodeInProgress{tag, current, goal}
226 case "BADEVENT":
227 // ../rfc/5465:1033
228 p.xspace()
229 p.xtake("(")
230 var l []string
231 for {
232 s := p.xatom()
233 l = append(l, s)
234 if !p.space() {
235 break
236 }
237 }
238 p.xtake(")")
239 return CodeBadEvent(l)
240
241 case "METADATA":
242 p.xspace()
243 if !p.take('(') {
244 p.xtake("LONGENTRIES")
245 p.xspace()
246 num := p.xuint32()
247 return CodeMetadataLongEntries(num)
248 }
249 w := strings.ToUpper(p.xatom())
250 switch w {
251 case "MAXSIZE":
252 p.xspace()
253 num := p.xuint32()
254 p.xtake(")")
255 return CodeMetadataMaxSize(num)
256 case "TOOMANY":
257 p.xtake(")")
258 return CodeMetadataTooMany{}
259 case "NOPRIVATE":
260 p.xtake(")")
261 return CodeMetadataNoPrivate{}
262 }
263 p.xerrorf("parsing METADATA response code, got %q, expected one of MAXSIZE, TOOMANY, NOPRIVATE", w)
264 panic("not reached")
265
266 // Known codes without parameters.
267 case "ALERT",
268 "PARSE",
269 "READ-ONLY",
270 "READ-WRITE",
271 "TRYCREATE",
272 "UIDNOTSTICKY",
273 "UNAVAILABLE",
274 "AUTHENTICATIONFAILED",
275 "AUTHORIZATIONFAILED",
276 "EXPIRED",
277 "PRIVACYREQUIRED",
278 "CONTACTADMIN",
279 "NOPERM",
280 "INUSE",
281 "EXPUNGEISSUED",
282 "CORRUPTION",
283 "SERVERBUG",
284 "CLIENTBUG",
285 "CANNOT",
286 "LIMIT",
287 "ALREADYEXISTS",
288 "NONEXISTENT",
289 "NOTSAVED",
290 "HASCHILDREN",
291 "CLOSED",
292 "UNKNOWN-CTE",
293 "OVERQUOTA", // ../rfc/9208:472
294 "COMPRESSIONACTIVE", // ../rfc/4978:143
295 "NOTIFICATIONOVERFLOW", // ../rfc/5465:1023
296 "UIDREQUIRED": // ../rfc/9586:136
297 return CodeWord(W)
298
299 default:
300 var args []string
301 for p.space() {
302 arg := ""
303 for !p.peek(' ') && !p.peek(']') {
304 arg += string(rune(p.xbyte()))
305 }
306 args = append(args, arg)
307 }
308 if len(args) == 0 {
309 return CodeWord(W)
310 }
311 return CodeParams{W, args}
312 }
313}
314
315func (p *Proto) xbyte() byte {
316 b, err := p.readbyte()
317 p.xcheckf(err, "read byte")
318 return b
319}
320
321// take until b is seen. don't take b itself.
322func (p *Proto) xtakeuntil(b byte) string {
323 var s string
324 for {
325 x, err := p.readbyte()
326 p.xcheckf(err, "read byte")
327 if x == b {
328 p.xunreadbyte()
329 return s
330 }
331 s += string(rune(x))
332 }
333}
334
335func (p *Proto) xdigits() string {
336 var s string
337 for {
338 b, err := p.readbyte()
339 if err == nil && (b >= '0' && b <= '9') {
340 s += string(rune(b))
341 continue
342 }
343 p.xunreadbyte()
344 return s
345 }
346}
347
348func (p *Proto) peekdigit() bool {
349 if b, err := p.readbyte(); err == nil {
350 p.xunreadbyte()
351 return b >= '0' && b <= '9'
352 }
353 return false
354}
355
356func (p *Proto) xint32() int32 {
357 s := p.xdigits()
358 num, err := strconv.ParseInt(s, 10, 32)
359 p.xcheckf(err, "parsing int32")
360 return int32(num)
361}
362
363func (p *Proto) xint64() int64 {
364 s := p.xdigits()
365 num, err := strconv.ParseInt(s, 10, 63)
366 p.xcheckf(err, "parsing int64")
367 return num
368}
369
370func (p *Proto) xuint32() uint32 {
371 s := p.xdigits()
372 num, err := strconv.ParseUint(s, 10, 32)
373 p.xcheckf(err, "parsing uint32")
374 return uint32(num)
375}
376
377func (p *Proto) xnzuint32() uint32 {
378 v := p.xuint32()
379 if v == 0 {
380 p.xerrorf("got 0, expected nonzero uint")
381 }
382 return v
383}
384
385// todo: replace with proper parsing.
386func (p *Proto) xnonspace() string {
387 var s string
388 for !p.peek(' ') && !p.peek('\r') && !p.peek('\n') {
389 s += string(rune(p.xbyte()))
390 }
391 if s == "" {
392 p.xerrorf("expected non-space")
393 }
394 return s
395}
396
397// todo: replace with proper parsing
398func (p *Proto) xword() string {
399 return p.xatom()
400}
401
402// "*" SP is already consumed
403// ../rfc/9051:6868
404func (p *Proto) xuntagged() Untagged {
405 w := p.xnonspace()
406 W := strings.ToUpper(w)
407 switch W {
408 case "PREAUTH":
409 p.xspace()
410 code, text := p.xrespText()
411 r := UntaggedPreauth{code, text}
412 p.xcrlf()
413 return r
414
415 case "BYE":
416 p.xspace()
417 code, text := p.xrespText()
418 r := UntaggedBye{code, text}
419 p.xcrlf()
420 return r
421
422 case "OK", "NO", "BAD":
423 p.xspace()
424 r := UntaggedResult(p.xresult(Status(W)))
425 p.xcrlf()
426 return r
427
428 case "CAPABILITY":
429 // ../rfc/9051:6427
430 var caps []Capability
431 for p.space() {
432 s := p.xnonspace()
433 s = strings.ToUpper(s)
434 cc := Capability(s)
435 caps = append(caps, cc)
436 }
437 p.xcrlf()
438 return UntaggedCapability(caps)
439
440 case "ENABLED":
441 // ../rfc/9051:6520
442 var caps []Capability
443 for p.space() {
444 s := p.xnonspace()
445 s = strings.ToUpper(s)
446 cc := Capability(s)
447 caps = append(caps, cc)
448 }
449 p.xcrlf()
450 return UntaggedEnabled(caps)
451
452 case "FLAGS":
453 p.xspace()
454 r := UntaggedFlags(p.xflagList())
455 p.xcrlf()
456 return r
457
458 case "LIST":
459 p.xspace()
460 r := p.xmailboxList()
461 p.xcrlf()
462 return r
463
464 case "STATUS":
465 // ../rfc/9051:6681
466 p.xspace()
467 mailbox := p.xastring()
468 p.xspace()
469 p.xtake("(")
470 attrs := map[StatusAttr]int64{}
471 for !p.take(')') {
472 if len(attrs) > 0 {
473 p.xspace()
474 }
475 s := p.xatom()
476 p.xspace()
477 S := StatusAttr(strings.ToUpper(s))
478 var num int64
479 // ../rfc/9051:7059
480 switch S {
481 case "MESSAGES":
482 num = int64(p.xuint32())
483 case "UIDNEXT":
484 num = int64(p.xnzuint32())
485 case "UIDVALIDITY":
486 num = int64(p.xnzuint32())
487 case "UNSEEN":
488 num = int64(p.xuint32())
489 case "DELETED":
490 num = int64(p.xuint32())
491 case "SIZE":
492 num = p.xint64()
493 case "RECENT":
494 num = int64(p.xuint32())
495 case "APPENDLIMIT":
496 if p.peek('n') || p.peek('N') {
497 p.xtake("nil")
498 } else {
499 num = p.xint64()
500 }
501 case "HIGHESTMODSEQ":
502 num = p.xint64()
503 case "DELETED-STORAGE":
504 num = p.xint64()
505 default:
506 p.xerrorf("status: unknown attribute %q", s)
507 }
508 if _, ok := attrs[S]; ok {
509 p.xerrorf("status: duplicate attribute %q", s)
510 }
511 attrs[S] = num
512 }
513 r := UntaggedStatus{mailbox, attrs}
514 p.xcrlf()
515 return r
516
517 case "METADATA":
518 // ../rfc/5464:807
519 p.xspace()
520 mailbox := p.xastring()
521 p.xspace()
522 if !p.take('(') {
523 // Unsolicited form, with only annotation keys, not values.
524 var keys []string
525 for {
526 key := p.xastring()
527 keys = append(keys, key)
528 if !p.space() {
529 break
530 }
531 }
532 p.xcrlf()
533 return UntaggedMetadataKeys{mailbox, keys}
534 }
535
536 // Form with values, in response to GETMETADATA command.
537 r := UntaggedMetadataAnnotations{Mailbox: mailbox}
538 for {
539 key := p.xastring()
540 p.xspace()
541 var value []byte
542 var isString bool
543 if p.take('~') {
544 value = p.xliteral()
545 } else if p.peek('"') {
546 value = []byte(p.xstring())
547 isString = true
548 // note: the abnf also allows nstring, but that only makes sense when the
549 // production rule is used in the setmetadata command. ../rfc/5464:831
550 } else {
551 // For response to extended list.
552 p.xtake("nil")
553 }
554 r.Annotations = append(r.Annotations, Annotation{key, isString, value})
555
556 if p.take(')') {
557 break
558 }
559 p.xspace()
560 }
561 p.xcrlf()
562 return r
563
564 case "NAMESPACE":
565 // ../rfc/9051:6778
566 p.xspace()
567 personal := p.xnamespace()
568 p.xspace()
569 other := p.xnamespace()
570 p.xspace()
571 shared := p.xnamespace()
572 r := UntaggedNamespace{personal, other, shared}
573 p.xcrlf()
574 return r
575
576 case "SEARCH":
577 // ../rfc/9051:6809
578 var nums []uint32
579 for p.space() {
580 // ../rfc/7162:2557
581 if p.take('(') {
582 p.xtake("MODSEQ")
583 p.xspace()
584 modseq := p.xint64()
585 p.xtake(")")
586 p.xcrlf()
587 return UntaggedSearchModSeq{nums, modseq}
588 }
589 nums = append(nums, p.xnzuint32())
590 }
591 r := UntaggedSearch(nums)
592 p.xcrlf()
593 return r
594
595 case "ESEARCH":
596 r := p.xesearchResponse()
597 p.xcrlf()
598 return r
599
600 case "LSUB":
601 r := p.xlsub()
602 p.xcrlf()
603 return r
604
605 case "ID":
606 // ../rfc/2971:243
607 p.xspace()
608 var params map[string]string
609 if p.take('(') {
610 params = map[string]string{}
611 for !p.take(')') {
612 if len(params) > 0 {
613 p.xspace()
614 }
615 k := p.xstring()
616 p.xspace()
617 v := p.xnilString()
618 if _, ok := params[k]; ok {
619 p.xerrorf("duplicate key %q", k)
620 }
621 params[k] = v
622 }
623 } else {
624 p.xtake("nil")
625 }
626 p.xcrlf()
627 return UntaggedID(params)
628
629 // ../rfc/7162:2623
630 case "VANISHED":
631 p.xspace()
632 var earlier bool
633 if p.take('(') {
634 p.xtake("EARLIER")
635 p.xtake(")")
636 p.xspace()
637 earlier = true
638 }
639 uids := p.xuidset()
640 p.xcrlf()
641 return UntaggedVanished{earlier, NumSet{Ranges: uids}}
642
643 // ../rfc/9208:668 ../2087:242
644 case "QUOTAROOT":
645 p.xspace()
646 p.xastring()
647 var roots []string
648 for p.space() {
649 root := p.xastring()
650 roots = append(roots, root)
651 }
652 p.xcrlf()
653 return UntaggedQuotaroot(roots)
654
655 // ../rfc/9208:666 ../rfc/2087:239
656 case "QUOTA":
657 p.xspace()
658 root := p.xastring()
659 p.xspace()
660 p.xtake("(")
661
662 xresource := func() QuotaResource {
663 name := p.xatom()
664 p.xspace()
665 usage := p.xint64()
666 p.xspace()
667 limit := p.xint64()
668 return QuotaResource{QuotaResourceName(strings.ToUpper(name)), usage, limit}
669 }
670
671 seen := map[QuotaResourceName]bool{}
672 l := []QuotaResource{xresource()}
673 seen[l[0].Name] = true
674 for p.space() {
675 res := xresource()
676 if seen[res.Name] {
677 p.xerrorf("duplicate resource name %q", res.Name)
678 }
679 seen[res.Name] = true
680 l = append(l, res)
681 }
682 p.xtake(")")
683 p.xcrlf()
684 return UntaggedQuota{root, l}
685
686 default:
687 v, err := strconv.ParseUint(w, 10, 32)
688 if err == nil {
689 num := uint32(v)
690 p.xspace()
691 w = p.xword()
692 W = strings.ToUpper(w)
693 switch W {
694 case "FETCH", "UIDFETCH":
695 if num == 0 {
696 p.xerrorf("invalid zero number for untagged fetch response")
697 }
698 p.xspace()
699 attrs := p.xfetch()
700 p.xcrlf()
701 if W == "UIDFETCH" {
702 return UntaggedUIDFetch{num, attrs}
703 }
704 return UntaggedFetch{num, attrs}
705
706 case "EXPUNGE":
707 if num == 0 {
708 p.xerrorf("invalid zero number for untagged expunge response")
709 }
710 p.xcrlf()
711 return UntaggedExpunge(num)
712
713 case "EXISTS":
714 p.xcrlf()
715 return UntaggedExists(num)
716
717 case "RECENT":
718 p.xcrlf()
719 return UntaggedRecent(num)
720
721 default:
722 p.xerrorf("unknown untagged numbered response %q", w)
723 panic("not reached")
724 }
725 }
726 p.xerrorf("unknown untagged response %q", w)
727 }
728 panic("not reached")
729}
730
731// ../rfc/3501:4864 ../rfc/9051:6742
732// Already parsed: "*" SP nznumber SP "FETCH" SP
733func (p *Proto) xfetch() []FetchAttr {
734 p.xtake("(")
735 attrs := []FetchAttr{p.xmsgatt1()}
736 for p.space() {
737 attrs = append(attrs, p.xmsgatt1())
738 }
739 p.xtake(")")
740 return attrs
741}
742
743// ../rfc/9051:6746
744func (p *Proto) xmsgatt1() FetchAttr {
745 f := ""
746 for {
747 b := p.xbyte()
748 if b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b >= '0' && b <= '9' || b == '.' {
749 f += string(rune(b))
750 continue
751 }
752 p.xunreadbyte()
753 break
754 }
755
756 F := strings.ToUpper(f)
757 switch F {
758 case "FLAGS":
759 p.xspace()
760 p.xtake("(")
761 var flags []string
762 if !p.take(')') {
763 flags = []string{p.xflag()}
764 for p.space() {
765 flags = append(flags, p.xflag())
766 }
767 p.xtake(")")
768 }
769 return FetchFlags(flags)
770
771 case "ENVELOPE":
772 p.xspace()
773 return FetchEnvelope(p.xenvelope())
774
775 case "INTERNALDATE":
776 p.xspace()
777 s := p.xquoted()
778 v, err := time.Parse("_2-Jan-2006 15:04:05 -0700", s)
779 p.xcheckf(err, "parsing internaldate")
780 return FetchInternalDate{v}
781
782 case "SAVEDATE":
783 p.xspace()
784 var t *time.Time
785 if p.peek('"') {
786 s := p.xquoted()
787 v, err := time.Parse("_2-Jan-2006 15:04:05 -0700", s)
788 p.xcheckf(err, "parsing savedate")
789 t = &v
790 } else {
791 p.xtake("nil")
792 }
793 return FetchSaveDate{t}
794
795 case "RFC822.SIZE":
796 p.xspace()
797 return FetchRFC822Size(p.xint64())
798
799 case "RFC822":
800 p.xspace()
801 s := p.xnilString()
802 return FetchRFC822(s)
803
804 case "RFC822.HEADER":
805 p.xspace()
806 s := p.xnilString()
807 return FetchRFC822Header(s)
808
809 case "RFC822.TEXT":
810 p.xspace()
811 s := p.xnilString()
812 return FetchRFC822Text(s)
813
814 case "BODY":
815 if p.space() {
816 return FetchBodystructure{F, p.xbodystructure(false)}
817 }
818 p.record = true
819 section := p.xsection()
820 var offset int32
821 if p.take('<') {
822 offset = p.xint32()
823 p.xtake(">")
824 }
825 F += p.recorded()
826 p.xspace()
827 body := p.xnilString()
828 return FetchBody{F, section, offset, body}
829
830 case "BODYSTRUCTURE":
831 p.xspace()
832 return FetchBodystructure{F, p.xbodystructure(true)}
833
834 case "BINARY":
835 p.record = true
836 nums := p.xsectionBinary()
837 F += p.recorded()
838 p.xspace()
839 buf := p.xnilStringLiteral8()
840 return FetchBinary{F, nums, string(buf)}
841
842 case "BINARY.SIZE":
843 p.record = true
844 nums := p.xsectionBinary()
845 F += p.recorded()
846 p.xspace()
847 size := p.xint64()
848 return FetchBinarySize{F, nums, size}
849
850 case "UID":
851 p.xspace()
852 return FetchUID(p.xuint32())
853
854 case "MODSEQ":
855 // ../rfc/7162:2488
856 p.xspace()
857 p.xtake("(")
858 modseq := p.xint64()
859 p.xtake(")")
860 return FetchModSeq(modseq)
861
862 case "PREVIEW":
863 // ../rfc/8970:348
864 p.xspace()
865 var preview *string
866 if p.peek('n') || p.peek('N') {
867 p.xtake("nil")
868 } else {
869 s := p.xstring()
870 preview = &s
871 }
872 return FetchPreview{preview}
873 }
874 p.xerrorf("unknown fetch attribute %q", f)
875 panic("not reached")
876}
877
878func (p *Proto) xnilString() string {
879 if p.peek('"') {
880 return p.xquoted()
881 } else if p.peek('{') {
882 return string(p.xliteral())
883 } else {
884 p.xtake("nil")
885 return ""
886 }
887}
888
889func ptr[T any](v T) *T {
890 return &v
891}
892
893func (p *Proto) xnilptrString() *string {
894 if p.peek('"') {
895 return ptr(p.xquoted())
896 } else if p.peek('{') {
897 return ptr(string(p.xliteral()))
898 } else {
899 p.xtake("nil")
900 return nil
901 }
902}
903
904func (p *Proto) xstring() string {
905 if p.peek('"') {
906 return p.xquoted()
907 }
908 return string(p.xliteral())
909}
910
911func (p *Proto) xastring() string {
912 if p.peek('"') {
913 return p.xquoted()
914 } else if p.peek('{') {
915 return string(p.xliteral())
916 }
917 return p.xatom()
918}
919
920func (p *Proto) xatom() string {
921 var s string
922 for {
923 b, err := p.readbyte()
924 p.xcheckf(err, "read byte for atom")
925 if b <= ' ' || strings.IndexByte("(){%*\"\\]", b) >= 0 {
926 p.xunreadbyte()
927 if s == "" {
928 p.xerrorf("expected atom")
929 }
930 return s
931 }
932 s += string(rune(b))
933 }
934}
935
936// ../rfc/9051:6856 ../rfc/6855:153
937func (p *Proto) xquoted() string {
938 p.xtake(`"`)
939 s := ""
940 for !p.take('"') {
941 r, err := p.readrune()
942 p.xcheckf(err, "reading rune in quoted string")
943 if r == '\\' {
944 r, err = p.readrune()
945 p.xcheckf(err, "reading escaped char in quoted string")
946 if r != '\\' && r != '"' {
947 p.xerrorf("quoted char not backslash or dquote: %c", r)
948 }
949 }
950 // todo: probably refuse some more chars. like \0 and all ctl and backspace.
951 s += string(r)
952 }
953 return s
954}
955
956func (p *Proto) xliteral() []byte {
957 p.xtake("{")
958 size := p.xint64()
959 sync := p.take('+')
960 p.xtake("}")
961 p.xcrlf()
962 // todo: for some literals, read as tracedata
963 if size > 1<<20 {
964 p.xerrorf("refusing to read more than 1MB: %d", size)
965 }
966 if sync {
967 if p.xbw == nil {
968 p.xerrorf("cannot parse literals without connection")
969 }
970 fmt.Fprintf(p.xbw, "+ ok\r\n")
971 p.xflush()
972 }
973 buf := make([]byte, int(size))
974 defer p.xtraceread(mlog.LevelTracedata)()
975 _, err := io.ReadFull(p.br, buf)
976 p.xcheckf(err, "reading data for literal")
977 p.xtraceread(mlog.LevelTrace)
978 return buf
979}
980
981// ../rfc/9051:6565
982// todo: stricter
983func (p *Proto) xflag0(allowPerm bool) string {
984 s := ""
985 if p.take('\\') {
986 s = `\`
987 if allowPerm && p.take('*') {
988 return `\*`
989 }
990 } else if p.take('$') {
991 s = "$"
992 }
993 s += p.xatom()
994 return s
995}
996
997func (p *Proto) xflag() string {
998 return p.xflag0(false)
999}
1000
1001func (p *Proto) xflagPerm() string {
1002 return p.xflag0(true)
1003}
1004
1005func (p *Proto) xsection() string {
1006 p.xtake("[")
1007 s := p.xtakeuntil(']')
1008 p.xtake("]")
1009 return s
1010}
1011
1012func (p *Proto) xsectionBinary() []uint32 {
1013 p.xtake("[")
1014 var nums []uint32
1015 for !p.take(']') {
1016 if len(nums) > 0 {
1017 p.xtake(".")
1018 }
1019 nums = append(nums, p.xnzuint32())
1020 }
1021 return nums
1022}
1023
1024func (p *Proto) xnilStringLiteral8() []byte {
1025 // todo: should make difference for literal8 and literal from string, which bytes are allowed
1026 if p.take('~') || p.peek('{') {
1027 return p.xliteral()
1028 }
1029 return []byte(p.xnilString())
1030}
1031
1032// ../rfc/9051:6355
1033func (p *Proto) xbodystructure(extensibleForm bool) any {
1034 p.xtake("(")
1035 if p.peek('(') {
1036 // ../rfc/9051:6411
1037 parts := []any{p.xbodystructure(extensibleForm)}
1038 for p.peek('(') {
1039 parts = append(parts, p.xbodystructure(extensibleForm))
1040 }
1041 p.xspace()
1042 mediaSubtype := p.xstring()
1043 var ext *BodyExtensionMpart
1044 if extensibleForm && p.space() {
1045 ext = p.xbodyExtMpart()
1046 }
1047 p.xtake(")")
1048 return BodyTypeMpart{parts, mediaSubtype, ext}
1049 }
1050
1051 // todo: verify the media(sub)type is valid for returned data.
1052
1053 var ext *BodyExtension1Part
1054 mediaType := p.xstring()
1055 p.xspace()
1056 mediaSubtype := p.xstring()
1057 p.xspace()
1058 bodyFields := p.xbodyFields()
1059 if !p.space() {
1060 // Basic type without extension.
1061 p.xtake(")")
1062 return BodyTypeBasic{mediaType, mediaSubtype, bodyFields, nil}
1063 }
1064 if p.peek('(') {
1065 // ../rfc/9051:6415
1066 envelope := p.xenvelope()
1067 p.xspace()
1068 bodyStructure := p.xbodystructure(extensibleForm)
1069 p.xspace()
1070 lines := p.xint64()
1071 if extensibleForm && p.space() {
1072 ext = p.xbodyExt1Part()
1073 }
1074 p.xtake(")")
1075 return BodyTypeMsg{mediaType, mediaSubtype, bodyFields, envelope, bodyStructure, lines, ext}
1076 }
1077 if !strings.EqualFold(mediaType, "text") {
1078 if !extensibleForm {
1079 p.xerrorf("body result, basic type, with disallowed extensible form")
1080 }
1081 ext = p.xbodyExt1Part()
1082 // ../rfc/9051:6407
1083 p.xtake(")")
1084 return BodyTypeBasic{mediaType, mediaSubtype, bodyFields, ext}
1085 }
1086 // ../rfc/9051:6418
1087 lines := p.xint64()
1088 if extensibleForm && p.space() {
1089 ext = p.xbodyExt1Part()
1090 }
1091 p.xtake(")")
1092 return BodyTypeText{mediaType, mediaSubtype, bodyFields, lines, ext}
1093}
1094
1095// ../rfc/9051:6376 ../rfc/3501:4604
1096func (p *Proto) xbodyFields() BodyFields {
1097 params := p.xbodyFldParam()
1098 p.xspace()
1099 contentID := p.xnilString()
1100 p.xspace()
1101 contentDescr := p.xnilString()
1102 p.xspace()
1103 cte := p.xnilString()
1104 p.xspace()
1105 octets := p.xint32()
1106 return BodyFields{params, contentID, contentDescr, cte, octets}
1107}
1108
1109// ../rfc/9051:6371 ../rfc/3501:4599
1110func (p *Proto) xbodyExtMpart() (ext *BodyExtensionMpart) {
1111 ext = &BodyExtensionMpart{}
1112 ext.Params = p.xbodyFldParam()
1113 if !p.space() {
1114 return
1115 }
1116 disp, dispParams := p.xbodyFldDsp()
1117 ext.Disposition, ext.DispositionParams = &disp, &dispParams
1118 if !p.space() {
1119 return
1120 }
1121 ext.Language = ptr(p.xbodyFldLang())
1122 if !p.space() {
1123 return
1124 }
1125 ext.Location = ptr(p.xbodyFldLoc())
1126 for p.space() {
1127 ext.More = append(ext.More, p.xbodyExtension())
1128 }
1129 return
1130}
1131
1132// ../rfc/9051:6366 ../rfc/3501:4584
1133func (p *Proto) xbodyExt1Part() (ext *BodyExtension1Part) {
1134 ext = &BodyExtension1Part{}
1135 ext.MD5 = p.xnilptrString()
1136 if !p.space() {
1137 return
1138 }
1139 disp, dispParams := p.xbodyFldDsp()
1140 ext.Disposition, ext.DispositionParams = &disp, &dispParams
1141 if !p.space() {
1142 return
1143 }
1144 ext.Language = ptr(p.xbodyFldLang())
1145 if !p.space() {
1146 return
1147 }
1148 ext.Location = ptr(p.xbodyFldLoc())
1149 for p.space() {
1150 ext.More = append(ext.More, p.xbodyExtension())
1151 }
1152 return
1153}
1154
1155// ../rfc/9051:6401 ../rfc/3501:4626
1156func (p *Proto) xbodyFldParam() [][2]string {
1157 if p.take('(') {
1158 k := p.xstring()
1159 p.xspace()
1160 v := p.xstring()
1161 l := [][2]string{{k, v}}
1162 for p.space() {
1163 k = p.xstring()
1164 p.xspace()
1165 v = p.xstring()
1166 l = append(l, [2]string{k, v})
1167 }
1168 p.xtake(")")
1169 return l
1170 }
1171 p.xtake("nil")
1172 return nil
1173}
1174
1175// ../rfc/9051:6381 ../rfc/3501:4609
1176func (p *Proto) xbodyFldDsp() (*string, [][2]string) {
1177 if !p.take('(') {
1178 p.xtake("nil")
1179 return nil, nil
1180 }
1181 disposition := p.xstring()
1182 p.xspace()
1183 param := p.xbodyFldParam()
1184 p.xtake(")")
1185 return ptr(disposition), param
1186}
1187
1188// ../rfc/9051:6391 ../rfc/3501:4616
1189func (p *Proto) xbodyFldLang() (lang []string) {
1190 if p.take('(') {
1191 lang = []string{p.xstring()}
1192 for p.space() {
1193 lang = append(lang, p.xstring())
1194 }
1195 p.xtake(")")
1196 return lang
1197 }
1198 if p.peekstring() {
1199 return []string{p.xstring()}
1200 }
1201 p.xtake("nil")
1202 return nil
1203}
1204
1205// ../rfc/9051:6393 ../rfc/3501:4618
1206func (p *Proto) xbodyFldLoc() *string {
1207 return p.xnilptrString()
1208}
1209
1210// ../rfc/9051:6357 ../rfc/3501:4575
1211func (p *Proto) xbodyExtension() (ext BodyExtension) {
1212 if p.take('(') {
1213 for {
1214 ext.More = append(ext.More, p.xbodyExtension())
1215 if !p.space() {
1216 break
1217 }
1218 }
1219 p.xtake(")")
1220 } else if p.peekdigit() {
1221 num := p.xint64()
1222 ext.Number = &num
1223 } else if p.peekstring() {
1224 str := p.xstring()
1225 ext.String = &str
1226 } else {
1227 p.xtake("nil")
1228 }
1229 return ext
1230}
1231
1232// ../rfc/9051:6522
1233func (p *Proto) xenvelope() Envelope {
1234 p.xtake("(")
1235 date := p.xnilString()
1236 p.xspace()
1237 subject := p.xnilString()
1238 p.xspace()
1239 from := p.xaddresses()
1240 p.xspace()
1241 sender := p.xaddresses()
1242 p.xspace()
1243 replyTo := p.xaddresses()
1244 p.xspace()
1245 to := p.xaddresses()
1246 p.xspace()
1247 cc := p.xaddresses()
1248 p.xspace()
1249 bcc := p.xaddresses()
1250 p.xspace()
1251 inReplyTo := p.xnilString()
1252 p.xspace()
1253 messageID := p.xnilString()
1254 p.xtake(")")
1255 return Envelope{date, subject, from, sender, replyTo, to, cc, bcc, inReplyTo, messageID}
1256}
1257
1258// ../rfc/9051:6526
1259func (p *Proto) xaddresses() []Address {
1260 if !p.take('(') {
1261 p.xtake("nil")
1262 return nil
1263 }
1264 l := []Address{p.xaddress()}
1265 for !p.take(')') {
1266 l = append(l, p.xaddress())
1267 }
1268 return l
1269}
1270
1271// ../rfc/9051:6303
1272func (p *Proto) xaddress() Address {
1273 p.xtake("(")
1274 name := p.xnilString()
1275 p.xspace()
1276 adl := p.xnilString()
1277 p.xspace()
1278 mailbox := p.xnilString()
1279 p.xspace()
1280 host := p.xnilString()
1281 p.xtake(")")
1282 return Address{name, adl, mailbox, host}
1283}
1284
1285// ../rfc/9051:6584
1286func (p *Proto) xflagList() []string {
1287 p.xtake("(")
1288 var l []string
1289 if !p.take(')') {
1290 l = []string{p.xflag()}
1291 for p.space() {
1292 l = append(l, p.xflag())
1293 }
1294 p.xtake(")")
1295 }
1296 return l
1297}
1298
1299// ../rfc/9051:6690
1300func (p *Proto) xmailboxList() UntaggedList {
1301 p.xtake("(")
1302 var flags []string
1303 if !p.peek(')') {
1304 flags = append(flags, p.xflag())
1305 for p.space() {
1306 flags = append(flags, p.xflag())
1307 }
1308 }
1309 p.xtake(")")
1310 p.xspace()
1311 var quoted string
1312 var b byte
1313 if p.peek('"') {
1314 quoted = p.xquoted()
1315 if len(quoted) != 1 {
1316 p.xerrorf("mailbox-list has multichar quoted part: %q", quoted)
1317 }
1318 b = byte(quoted[0])
1319 } else if !p.peek(' ') {
1320 p.xtake("nil")
1321 }
1322 p.xspace()
1323 mailbox := p.xastring()
1324 ul := UntaggedList{flags, b, mailbox, nil, ""}
1325 if p.space() {
1326 p.xtake("(")
1327 if !p.peek(')') {
1328 p.xmboxListExtendedItem(&ul)
1329 for p.space() {
1330 p.xmboxListExtendedItem(&ul)
1331 }
1332 }
1333 p.xtake(")")
1334 }
1335 return ul
1336}
1337
1338// ../rfc/9051:6699
1339func (p *Proto) xmboxListExtendedItem(ul *UntaggedList) {
1340 tag := p.xastring()
1341 p.xspace()
1342 if strings.ToUpper(tag) == "OLDNAME" {
1343 // ../rfc/9051:6811
1344 p.xtake("(")
1345 name := p.xastring()
1346 p.xtake(")")
1347 ul.OldName = name
1348 return
1349 }
1350 val := p.xtaggedExtVal()
1351 ul.Extended = append(ul.Extended, MboxListExtendedItem{tag, val})
1352}
1353
1354// ../rfc/9051:7111
1355func (p *Proto) xtaggedExtVal() TaggedExtVal {
1356 if p.take('(') {
1357 var r TaggedExtVal
1358 if !p.take(')') {
1359 comp := p.xtaggedExtComp()
1360 r.Comp = &comp
1361 p.xtake(")")
1362 }
1363 return r
1364 }
1365 // We cannot just parse sequence-set, because we also have to accept number/number64. So first look for a number. If it is not, we continue parsing the rest of the sequence set.
1366 b, err := p.readbyte()
1367 p.xcheckf(err, "read byte for tagged-ext-val")
1368 if b < '0' || b > '9' {
1369 p.xunreadbyte()
1370 ss := p.xsequenceSet()
1371 return TaggedExtVal{SeqSet: &ss}
1372 }
1373 s := p.xdigits()
1374 num, err := strconv.ParseInt(s, 10, 63)
1375 p.xcheckf(err, "parsing int")
1376 if !p.peek(':') && !p.peek(',') {
1377 // not a larger sequence-set
1378 return TaggedExtVal{Number: &num}
1379 }
1380 var sr NumRange
1381 sr.First = uint32(num)
1382 if p.take(':') {
1383 var num uint32
1384 if !p.take('*') {
1385 num = p.xnzuint32()
1386 }
1387 sr.Last = &num
1388 }
1389 ss := p.xsequenceSet()
1390 ss.Ranges = append([]NumRange{sr}, ss.Ranges...)
1391 return TaggedExtVal{SeqSet: &ss}
1392}
1393
1394// ../rfc/9051:7034
1395func (p *Proto) xsequenceSet() NumSet {
1396 if p.take('$') {
1397 return NumSet{SearchResult: true}
1398 }
1399 var ss NumSet
1400 for {
1401 var sr NumRange
1402 if !p.take('*') {
1403 sr.First = p.xnzuint32()
1404 }
1405 if p.take(':') {
1406 var num uint32
1407 if !p.take('*') {
1408 num = p.xnzuint32()
1409 }
1410 sr.Last = &num
1411 }
1412 ss.Ranges = append(ss.Ranges, sr)
1413 if !p.take(',') {
1414 break
1415 }
1416 }
1417 return ss
1418}
1419
1420// ../rfc/9051:7097
1421func (p *Proto) xtaggedExtComp() TaggedExtComp {
1422 if p.take('(') {
1423 r := p.xtaggedExtComp()
1424 p.xtake(")")
1425 return TaggedExtComp{Comps: []TaggedExtComp{r}}
1426 }
1427 s := p.xastring()
1428 if !p.peek(' ') {
1429 return TaggedExtComp{String: s}
1430 }
1431 l := []TaggedExtComp{{String: s}}
1432 for p.space() {
1433 l = append(l, p.xtaggedExtComp())
1434 }
1435 return TaggedExtComp{Comps: l}
1436}
1437
1438// ../rfc/9051:6765
1439func (p *Proto) xnamespace() []NamespaceDescr {
1440 if !p.take('(') {
1441 p.xtake("nil")
1442 return nil
1443 }
1444
1445 l := []NamespaceDescr{p.xnamespaceDescr()}
1446 for !p.take(')') {
1447 l = append(l, p.xnamespaceDescr())
1448 }
1449 return l
1450}
1451
1452// ../rfc/9051:6769
1453func (p *Proto) xnamespaceDescr() NamespaceDescr {
1454 p.xtake("(")
1455 prefix := p.xstring()
1456 p.xspace()
1457 var b byte
1458 if p.peek('"') {
1459 s := p.xquoted()
1460 if len(s) != 1 {
1461 p.xerrorf("namespace-descr: expected single char, got %q", s)
1462 }
1463 b = byte(s[0])
1464 } else {
1465 p.xtake("nil")
1466 }
1467 var exts []NamespaceExtension
1468 for !p.take(')') {
1469 p.xspace()
1470 key := p.xstring()
1471 p.xspace()
1472 p.xtake("(")
1473 values := []string{p.xstring()}
1474 for p.space() {
1475 values = append(values, p.xstring())
1476 }
1477 p.xtake(")")
1478 exts = append(exts, NamespaceExtension{key, values})
1479 }
1480 return NamespaceDescr{prefix, b, exts}
1481}
1482
1483// ../rfc/9051:6546
1484// Already consumed: "ESEARCH"
1485func (p *Proto) xesearchResponse() (r UntaggedEsearch) {
1486 if !p.space() {
1487 return
1488 }
1489
1490 if p.take('(') {
1491 // ../rfc/9051:6921 ../rfc/7377:465
1492 seen := map[string]bool{}
1493 for {
1494 var kind string
1495 if p.peek('t') || p.peek('T') {
1496 kind = "TAG"
1497 p.xtake(kind)
1498 p.xspace()
1499 r.Tag = p.xastring()
1500 } else if p.peek('m') || p.peek('M') {
1501 kind = "MAILBOX"
1502 p.xtake(kind)
1503 p.xspace()
1504 r.Mailbox = p.xastring()
1505 if r.Mailbox == "" {
1506 p.xerrorf("invalid empty mailbox in search correlator")
1507 }
1508 } else if p.peek('u') || p.peek('U') {
1509 kind = "UIDVALIDITY"
1510 p.xtake(kind)
1511 p.xspace()
1512 r.UIDValidity = p.xnzuint32()
1513 } else {
1514 p.xerrorf("expected tag/correlator, mailbox or uidvalidity")
1515 }
1516
1517 if seen[kind] {
1518 p.xerrorf("duplicate search correlator %q", kind)
1519 }
1520 seen[kind] = true
1521
1522 if !p.take(' ') {
1523 break
1524 }
1525 }
1526
1527 if r.Tag == "" {
1528 p.xerrorf("missing tag search correlator")
1529 }
1530 if (r.Mailbox != "") != (r.UIDValidity != 0) {
1531 p.xerrorf("mailbox and uidvalidity correlators must both be absent or both be present")
1532 }
1533
1534 p.xtake(")")
1535 }
1536 if !p.space() {
1537 return
1538 }
1539 w := p.xnonspace()
1540 W := strings.ToUpper(w)
1541 if W == "UID" {
1542 r.UID = true
1543 if !p.space() {
1544 return
1545 }
1546 w = p.xnonspace()
1547 W = strings.ToUpper(w)
1548 }
1549 for {
1550 // ../rfc/9051:6957
1551 switch W {
1552 case "MIN":
1553 if r.Min != 0 {
1554 p.xerrorf("duplicate MIN in ESEARCH")
1555 }
1556 p.xspace()
1557 num := p.xnzuint32()
1558 r.Min = num
1559
1560 case "MAX":
1561 if r.Max != 0 {
1562 p.xerrorf("duplicate MAX in ESEARCH")
1563 }
1564 p.xspace()
1565 num := p.xnzuint32()
1566 r.Max = num
1567
1568 case "ALL":
1569 if !r.All.IsZero() {
1570 p.xerrorf("duplicate ALL in ESEARCH")
1571 }
1572 p.xspace()
1573 ss := p.xsequenceSet()
1574 if ss.SearchResult {
1575 p.xerrorf("$ for last not valid in ESEARCH")
1576 }
1577 r.All = ss
1578
1579 case "COUNT":
1580 if r.Count != nil {
1581 p.xerrorf("duplicate COUNT in ESEARCH")
1582 }
1583 p.xspace()
1584 num := p.xuint32()
1585 r.Count = &num
1586
1587 // ../rfc/7162:1211 ../rfc/4731:273
1588 case "MODSEQ":
1589 p.xspace()
1590 r.ModSeq = p.xint64()
1591
1592 default:
1593 // Validate ../rfc/9051:7090
1594 for i, b := range []byte(w) {
1595 if !(b >= 'A' && b <= 'Z' || strings.IndexByte("-_.", b) >= 0 || i > 0 && strings.IndexByte("0123456789:", b) >= 0) {
1596 p.xerrorf("invalid tag %q", w)
1597 }
1598 }
1599 p.xspace()
1600 ext := EsearchDataExt{w, p.xtaggedExtVal()}
1601 r.Exts = append(r.Exts, ext)
1602 }
1603
1604 if !p.space() {
1605 break
1606 }
1607 w = p.xnonspace() // todo: this is too loose
1608 W = strings.ToUpper(w)
1609 }
1610 return
1611}
1612
1613// ../rfc/9051:6441
1614func (p *Proto) xcharset() string {
1615 if p.peek('"') {
1616 return p.xquoted()
1617 }
1618 return p.xatom()
1619}
1620
1621// ../rfc/9051:7133
1622func (p *Proto) xuidset() []NumRange {
1623 ranges := []NumRange{p.xuidrange()}
1624 for p.take(',') {
1625 ranges = append(ranges, p.xuidrange())
1626 }
1627 return ranges
1628}
1629
1630func (p *Proto) xuidrange() NumRange {
1631 uid := p.xnzuint32()
1632 var end *uint32
1633 if p.take(':') {
1634 x := p.xnzuint32()
1635 end = &x
1636 }
1637 return NumRange{uid, end}
1638}
1639
1640// ../rfc/3501:4833
1641func (p *Proto) xlsub() UntaggedLsub {
1642 p.xspace()
1643 p.xtake("(")
1644 r := UntaggedLsub{}
1645 for !p.take(')') {
1646 if len(r.Flags) > 0 {
1647 p.xspace()
1648 }
1649 r.Flags = append(r.Flags, p.xflag())
1650 }
1651 p.xspace()
1652 if p.peek('"') {
1653 s := p.xquoted()
1654 if !p.peek(' ') {
1655 r.Mailbox = s
1656 return r
1657 }
1658 if len(s) != 1 {
1659 // todo: check valid char
1660 p.xerrorf("invalid separator %q", s)
1661 }
1662 r.Separator = byte(s[0])
1663 }
1664 p.xspace()
1665 r.Mailbox = p.xastring()
1666 return r
1667}
1668