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