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