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