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