Source file
src/net/mail/message.go
1
2
3
4
5
18 package mail
19
20 import (
21 "bufio"
22 "errors"
23 "fmt"
24 "io"
25 "log"
26 "mime"
27 "net/textproto"
28 "strings"
29 "time"
30 "unicode/utf8"
31 )
32
33 var debug = debugT(false)
34
35 type debugT bool
36
37 func (d debugT) Printf(format string, args ...interface{}) {
38 if d {
39 log.Printf(format, args...)
40 }
41 }
42
43
44 type Message struct {
45 Header Header
46 Body io.Reader
47 }
48
49
50
51
52 func ReadMessage(r io.Reader) (msg *Message, err error) {
53 tp := textproto.NewReader(bufio.NewReader(r))
54
55 hdr, err := tp.ReadMIMEHeader()
56 if err != nil {
57 return nil, err
58 }
59
60 return &Message{
61 Header: Header(hdr),
62 Body: tp.R,
63 }, nil
64 }
65
66
67
68 var dateLayouts []string
69
70 func init() {
71
72
73 dows := [...]string{"", "Mon, "}
74 days := [...]string{"2", "02"}
75 years := [...]string{"2006", "06"}
76 seconds := [...]string{":05", ""}
77
78 zones := [...]string{"-0700", "MST", "-0700 (MST)"}
79
80 for _, dow := range dows {
81 for _, day := range days {
82 for _, year := range years {
83 for _, second := range seconds {
84 for _, zone := range zones {
85 s := dow + day + " Jan " + year + " 15:04" + second + " " + zone
86 dateLayouts = append(dateLayouts, s)
87 }
88 }
89 }
90 }
91 }
92 }
93
94
95 func ParseDate(date string) (time.Time, error) {
96 for _, layout := range dateLayouts {
97 t, err := time.Parse(layout, date)
98 if err == nil {
99 return t, nil
100 }
101 }
102 return time.Time{}, errors.New("mail: header could not be parsed")
103 }
104
105
106 type Header map[string][]string
107
108
109
110
111
112
113
114 func (h Header) Get(key string) string {
115 return textproto.MIMEHeader(h).Get(key)
116 }
117
118 var ErrHeaderNotPresent = errors.New("mail: header not in message")
119
120
121 func (h Header) Date() (time.Time, error) {
122 hdr := h.Get("Date")
123 if hdr == "" {
124 return time.Time{}, ErrHeaderNotPresent
125 }
126 return ParseDate(hdr)
127 }
128
129
130 func (h Header) AddressList(key string) ([]*Address, error) {
131 hdr := h.Get(key)
132 if hdr == "" {
133 return nil, ErrHeaderNotPresent
134 }
135 return ParseAddressList(hdr)
136 }
137
138
139
140
141 type Address struct {
142 Name string
143 Address string
144 }
145
146
147 func ParseAddress(address string) (*Address, error) {
148 return (&addrParser{s: address}).parseSingleAddress()
149 }
150
151
152 func ParseAddressList(list string) ([]*Address, error) {
153 return (&addrParser{s: list}).parseAddressList()
154 }
155
156
157 type AddressParser struct {
158
159 WordDecoder *mime.WordDecoder
160 }
161
162
163
164 func (p *AddressParser) Parse(address string) (*Address, error) {
165 return (&addrParser{s: address, dec: p.WordDecoder}).parseSingleAddress()
166 }
167
168
169
170 func (p *AddressParser) ParseList(list string) ([]*Address, error) {
171 return (&addrParser{s: list, dec: p.WordDecoder}).parseAddressList()
172 }
173
174
175
176
177 func (a *Address) String() string {
178
179 at := strings.LastIndex(a.Address, "@")
180 var local, domain string
181 if at < 0 {
182
183
184 local = a.Address
185 } else {
186 local, domain = a.Address[:at], a.Address[at+1:]
187 }
188
189
190 quoteLocal := false
191 for i, r := range local {
192 if isAtext(r, false, false) {
193 continue
194 }
195 if r == '.' {
196
197
198
199 if i > 0 && local[i-1] != '.' && i < len(local)-1 {
200 continue
201 }
202 }
203 quoteLocal = true
204 break
205 }
206 if quoteLocal {
207 local = quoteString(local)
208
209 }
210
211 s := "<" + local + "@" + domain + ">"
212
213 if a.Name == "" {
214 return s
215 }
216
217
218 allPrintable := true
219 for _, r := range a.Name {
220
221
222 if !isVchar(r) && !isWSP(r) || isMultibyte(r) {
223 allPrintable = false
224 break
225 }
226 }
227 if allPrintable {
228 return quoteString(a.Name) + " " + s
229 }
230
231
232
233
234 if strings.ContainsAny(a.Name, "\"#$%&'(),.:;<>@[]^`{|}~") {
235 return mime.BEncoding.Encode("utf-8", a.Name) + " " + s
236 }
237 return mime.QEncoding.Encode("utf-8", a.Name) + " " + s
238 }
239
240 type addrParser struct {
241 s string
242 dec *mime.WordDecoder
243 }
244
245 func (p *addrParser) parseAddressList() ([]*Address, error) {
246 var list []*Address
247 for {
248 p.skipSpace()
249 addrs, err := p.parseAddress(true)
250 if err != nil {
251 return nil, err
252 }
253 list = append(list, addrs...)
254
255 if !p.skipCFWS() {
256 return nil, errors.New("mail: misformatted parenthetical comment")
257 }
258 if p.empty() {
259 break
260 }
261 if !p.consume(',') {
262 return nil, errors.New("mail: expected comma")
263 }
264 }
265 return list, nil
266 }
267
268 func (p *addrParser) parseSingleAddress() (*Address, error) {
269 addrs, err := p.parseAddress(true)
270 if err != nil {
271 return nil, err
272 }
273 if !p.skipCFWS() {
274 return nil, errors.New("mail: misformatted parenthetical comment")
275 }
276 if !p.empty() {
277 return nil, fmt.Errorf("mail: expected single address, got %q", p.s)
278 }
279 if len(addrs) == 0 {
280 return nil, errors.New("mail: empty group")
281 }
282 if len(addrs) > 1 {
283 return nil, errors.New("mail: group with multiple addresses")
284 }
285 return addrs[0], nil
286 }
287
288
289 func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
290 debug.Printf("parseAddress: %q", p.s)
291 p.skipSpace()
292 if p.empty() {
293 return nil, errors.New("mail: no address")
294 }
295
296
297
298
299
300
301
302
303 spec, err := p.consumeAddrSpec()
304 if err == nil {
305 var displayName string
306 p.skipSpace()
307 if !p.empty() && p.peek() == '(' {
308 displayName, err = p.consumeDisplayNameComment()
309 if err != nil {
310 return nil, err
311 }
312 }
313
314 return []*Address{{
315 Name: displayName,
316 Address: spec,
317 }}, err
318 }
319 debug.Printf("parseAddress: not an addr-spec: %v", err)
320 debug.Printf("parseAddress: state is now %q", p.s)
321
322
323 var displayName string
324 if p.peek() != '<' {
325 displayName, err = p.consumePhrase()
326 if err != nil {
327 return nil, err
328 }
329 }
330 debug.Printf("parseAddress: displayName=%q", displayName)
331
332 p.skipSpace()
333 if handleGroup {
334 if p.consume(':') {
335 return p.consumeGroupList()
336 }
337 }
338
339 if !p.consume('<') {
340 return nil, errors.New("mail: no angle-addr")
341 }
342 spec, err = p.consumeAddrSpec()
343 if err != nil {
344 return nil, err
345 }
346 if !p.consume('>') {
347 return nil, errors.New("mail: unclosed angle-addr")
348 }
349 debug.Printf("parseAddress: spec=%q", spec)
350
351 return []*Address{{
352 Name: displayName,
353 Address: spec,
354 }}, nil
355 }
356
357 func (p *addrParser) consumeGroupList() ([]*Address, error) {
358 var group []*Address
359
360 p.skipSpace()
361 if p.consume(';') {
362 p.skipCFWS()
363 return group, nil
364 }
365
366 for {
367 p.skipSpace()
368
369 addrs, err := p.parseAddress(false)
370 if err != nil {
371 return nil, err
372 }
373 group = append(group, addrs...)
374
375 if !p.skipCFWS() {
376 return nil, errors.New("mail: misformatted parenthetical comment")
377 }
378 if p.consume(';') {
379 p.skipCFWS()
380 break
381 }
382 if !p.consume(',') {
383 return nil, errors.New("mail: expected comma")
384 }
385 }
386 return group, nil
387 }
388
389
390 func (p *addrParser) consumeAddrSpec() (spec string, err error) {
391 debug.Printf("consumeAddrSpec: %q", p.s)
392
393 orig := *p
394 defer func() {
395 if err != nil {
396 *p = orig
397 }
398 }()
399
400
401 var localPart string
402 p.skipSpace()
403 if p.empty() {
404 return "", errors.New("mail: no addr-spec")
405 }
406 if p.peek() == '"' {
407
408 debug.Printf("consumeAddrSpec: parsing quoted-string")
409 localPart, err = p.consumeQuotedString()
410 if localPart == "" {
411 err = errors.New("mail: empty quoted string in addr-spec")
412 }
413 } else {
414
415 debug.Printf("consumeAddrSpec: parsing dot-atom")
416 localPart, err = p.consumeAtom(true, false)
417 }
418 if err != nil {
419 debug.Printf("consumeAddrSpec: failed: %v", err)
420 return "", err
421 }
422
423 if !p.consume('@') {
424 return "", errors.New("mail: missing @ in addr-spec")
425 }
426
427
428 var domain string
429 p.skipSpace()
430 if p.empty() {
431 return "", errors.New("mail: no domain in addr-spec")
432 }
433
434 domain, err = p.consumeAtom(true, false)
435 if err != nil {
436 return "", err
437 }
438
439 return localPart + "@" + domain, nil
440 }
441
442
443 func (p *addrParser) consumePhrase() (phrase string, err error) {
444 debug.Printf("consumePhrase: [%s]", p.s)
445
446 var words []string
447 var isPrevEncoded bool
448 for {
449
450 var word string
451 p.skipSpace()
452 if p.empty() {
453 break
454 }
455 isEncoded := false
456 if p.peek() == '"' {
457
458 word, err = p.consumeQuotedString()
459 } else {
460
461
462
463 word, err = p.consumeAtom(true, true)
464 if err == nil {
465 word, isEncoded, err = p.decodeRFC2047Word(word)
466 }
467 }
468
469 if err != nil {
470 break
471 }
472 debug.Printf("consumePhrase: consumed %q", word)
473 if isPrevEncoded && isEncoded {
474 words[len(words)-1] += word
475 } else {
476 words = append(words, word)
477 }
478 isPrevEncoded = isEncoded
479 }
480
481 if err != nil && len(words) == 0 {
482 debug.Printf("consumePhrase: hit err: %v", err)
483 return "", fmt.Errorf("mail: missing word in phrase: %v", err)
484 }
485 phrase = strings.Join(words, " ")
486 return phrase, nil
487 }
488
489
490 func (p *addrParser) consumeQuotedString() (qs string, err error) {
491
492 i := 1
493 qsb := make([]rune, 0, 10)
494
495 escaped := false
496
497 Loop:
498 for {
499 r, size := utf8.DecodeRuneInString(p.s[i:])
500
501 switch {
502 case size == 0:
503 return "", errors.New("mail: unclosed quoted-string")
504
505 case size == 1 && r == utf8.RuneError:
506 return "", fmt.Errorf("mail: invalid utf-8 in quoted-string: %q", p.s)
507
508 case escaped:
509
510
511 if !isVchar(r) && !isWSP(r) {
512 return "", fmt.Errorf("mail: bad character in quoted-string: %q", r)
513 }
514
515 qsb = append(qsb, r)
516 escaped = false
517
518 case isQtext(r) || isWSP(r):
519
520
521 qsb = append(qsb, r)
522
523 case r == '"':
524 break Loop
525
526 case r == '\\':
527 escaped = true
528
529 default:
530 return "", fmt.Errorf("mail: bad character in quoted-string: %q", r)
531
532 }
533
534 i += size
535 }
536 p.s = p.s[i+1:]
537 return string(qsb), nil
538 }
539
540
541
542
543
544
545 func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) {
546 i := 0
547
548 Loop:
549 for {
550 r, size := utf8.DecodeRuneInString(p.s[i:])
551 switch {
552 case size == 1 && r == utf8.RuneError:
553 return "", fmt.Errorf("mail: invalid utf-8 in address: %q", p.s)
554
555 case size == 0 || !isAtext(r, dot, permissive):
556 break Loop
557
558 default:
559 i += size
560
561 }
562 }
563
564 if i == 0 {
565 return "", errors.New("mail: invalid string")
566 }
567 atom, p.s = p.s[:i], p.s[i:]
568 if !permissive {
569 if strings.HasPrefix(atom, ".") {
570 return "", errors.New("mail: leading dot in atom")
571 }
572 if strings.Contains(atom, "..") {
573 return "", errors.New("mail: double dot in atom")
574 }
575 if strings.HasSuffix(atom, ".") {
576 return "", errors.New("mail: trailing dot in atom")
577 }
578 }
579 return atom, nil
580 }
581
582 func (p *addrParser) consumeDisplayNameComment() (string, error) {
583 if !p.consume('(') {
584 return "", errors.New("mail: comment does not start with (")
585 }
586 comment, ok := p.consumeComment()
587 if !ok {
588 return "", errors.New("mail: misformatted parenthetical comment")
589 }
590
591
592 words := strings.FieldsFunc(comment, func(r rune) bool { return r == ' ' || r == '\t' })
593 for idx, word := range words {
594 decoded, isEncoded, err := p.decodeRFC2047Word(word)
595 if err != nil {
596 return "", err
597 }
598 if isEncoded {
599 words[idx] = decoded
600 }
601 }
602
603 return strings.Join(words, " "), nil
604 }
605
606 func (p *addrParser) consume(c byte) bool {
607 if p.empty() || p.peek() != c {
608 return false
609 }
610 p.s = p.s[1:]
611 return true
612 }
613
614
615 func (p *addrParser) skipSpace() {
616 p.s = strings.TrimLeft(p.s, " \t")
617 }
618
619 func (p *addrParser) peek() byte {
620 return p.s[0]
621 }
622
623 func (p *addrParser) empty() bool {
624 return p.len() == 0
625 }
626
627 func (p *addrParser) len() int {
628 return len(p.s)
629 }
630
631
632 func (p *addrParser) skipCFWS() bool {
633 p.skipSpace()
634
635 for {
636 if !p.consume('(') {
637 break
638 }
639
640 if _, ok := p.consumeComment(); !ok {
641 return false
642 }
643
644 p.skipSpace()
645 }
646
647 return true
648 }
649
650 func (p *addrParser) consumeComment() (string, bool) {
651
652 depth := 1
653
654 var comment string
655 for {
656 if p.empty() || depth == 0 {
657 break
658 }
659
660 if p.peek() == '\\' && p.len() > 1 {
661 p.s = p.s[1:]
662 } else if p.peek() == '(' {
663 depth++
664 } else if p.peek() == ')' {
665 depth--
666 }
667 if depth > 0 {
668 comment += p.s[:1]
669 }
670 p.s = p.s[1:]
671 }
672
673 return comment, depth == 0
674 }
675
676 func (p *addrParser) decodeRFC2047Word(s string) (word string, isEncoded bool, err error) {
677 if p.dec != nil {
678 word, err = p.dec.Decode(s)
679 } else {
680 word, err = rfc2047Decoder.Decode(s)
681 }
682
683 if err == nil {
684 return word, true, nil
685 }
686
687 if _, ok := err.(charsetError); ok {
688 return s, true, err
689 }
690
691
692 return s, false, nil
693 }
694
695 var rfc2047Decoder = mime.WordDecoder{
696 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
697 return nil, charsetError(charset)
698 },
699 }
700
701 type charsetError string
702
703 func (e charsetError) Error() string {
704 return fmt.Sprintf("charset not supported: %q", string(e))
705 }
706
707
708
709
710
711 func isAtext(r rune, dot, permissive bool) bool {
712 switch r {
713 case '.':
714 return dot
715
716
717 case '(', ')', '[', ']', ';', '@', '\\', ',':
718 return permissive
719
720 case '<', '>', '"', ':':
721 return false
722 }
723 return isVchar(r)
724 }
725
726
727 func isQtext(r rune) bool {
728
729 if r == '\\' || r == '"' {
730 return false
731 }
732 return isVchar(r)
733 }
734
735
736 func quoteString(s string) string {
737 var buf strings.Builder
738 buf.WriteByte('"')
739 for _, r := range s {
740 if isQtext(r) || isWSP(r) {
741 buf.WriteRune(r)
742 } else if isVchar(r) {
743 buf.WriteByte('\\')
744 buf.WriteRune(r)
745 }
746 }
747 buf.WriteByte('"')
748 return buf.String()
749 }
750
751
752 func isVchar(r rune) bool {
753
754 return '!' <= r && r <= '~' || isMultibyte(r)
755 }
756
757
758
759 func isMultibyte(r rune) bool {
760 return r >= utf8.RuneSelf
761 }
762
763
764
765 func isWSP(r rune) bool {
766 return r == ' ' || r == '\t'
767 }
768
View as plain text