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