OSDN Git Service

bf22c711e49602ab3c1b62340e3a6b657ea9749f
[pf3gnuchains/gcc-fork.git] / libgo / go / net / mail / message.go
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 /*
6 Package mail implements parsing of mail messages.
7
8 For the most part, this package follows the syntax as specified by RFC 5322.
9 Notable divergences:
10         * Obsolete address formats are not parsed, including addresses with
11           embedded route information.
12         * Group addresses are not parsed.
13         * The full range of spacing (the CFWS syntax element) is not supported,
14           such as breaking addresses across lines.
15 */
16 package mail
17
18 import (
19         "bufio"
20         "bytes"
21         "encoding/base64"
22         "errors"
23         "fmt"
24         "io"
25         "io/ioutil"
26         "log"
27         "net/textproto"
28         "strconv"
29         "strings"
30         "time"
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 // A Message represents a parsed mail message.
44 type Message struct {
45         Header Header
46         Body   io.Reader
47 }
48
49 // ReadMessage reads a message from r.
50 // The headers are parsed, and the body of the message will be reading from r.
51 func ReadMessage(r io.Reader) (msg *Message, err error) {
52         tp := textproto.NewReader(bufio.NewReader(r))
53
54         hdr, err := tp.ReadMIMEHeader()
55         if err != nil {
56                 return nil, err
57         }
58
59         return &Message{
60                 Header: Header(hdr),
61                 Body:   tp.R,
62         }, nil
63 }
64
65 // Layouts suitable for passing to time.Parse.
66 // These are tried in order.
67 var dateLayouts []string
68
69 func init() {
70         // Generate layouts based on RFC 5322, section 3.3.
71
72         dows := [...]string{"", "Mon, "}     // day-of-week
73         days := [...]string{"2", "02"}       // day = 1*2DIGIT
74         years := [...]string{"2006", "06"}   // year = 4*DIGIT / 2*DIGIT
75         seconds := [...]string{":05", ""}    // second
76         zones := [...]string{"-0700", "MST"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ...
77
78         for _, dow := range dows {
79                 for _, day := range days {
80                         for _, year := range years {
81                                 for _, second := range seconds {
82                                         for _, zone := range zones {
83                                                 s := dow + day + " Jan " + year + " 15:04" + second + " " + zone
84                                                 dateLayouts = append(dateLayouts, s)
85                                         }
86                                 }
87                         }
88                 }
89         }
90 }
91
92 func parseDate(date string) (time.Time, error) {
93         for _, layout := range dateLayouts {
94                 t, err := time.Parse(layout, date)
95                 if err == nil {
96                         return t, nil
97                 }
98         }
99         return time.Time{}, errors.New("mail: header could not be parsed")
100 }
101
102 // A Header represents the key-value pairs in a mail message header.
103 type Header map[string][]string
104
105 // Get gets the first value associated with the given key.
106 // If there are no values associated with the key, Get returns "".
107 func (h Header) Get(key string) string {
108         return textproto.MIMEHeader(h).Get(key)
109 }
110
111 var ErrHeaderNotPresent = errors.New("mail: header not in message")
112
113 // Date parses the Date header field.
114 func (h Header) Date() (time.Time, error) {
115         hdr := h.Get("Date")
116         if hdr == "" {
117                 return time.Time{}, ErrHeaderNotPresent
118         }
119         return parseDate(hdr)
120 }
121
122 // AddressList parses the named header field as a list of addresses.
123 func (h Header) AddressList(key string) ([]*Address, error) {
124         hdr := h.Get(key)
125         if hdr == "" {
126                 return nil, ErrHeaderNotPresent
127         }
128         return newAddrParser(hdr).parseAddressList()
129 }
130
131 // Address represents a single mail address.
132 // An address such as "Barry Gibbs <bg@example.com>" is represented
133 // as Address{Name: "Barry Gibbs", Address: "bg@example.com"}.
134 type Address struct {
135         Name    string // Proper name; may be empty.
136         Address string // user@domain
137 }
138
139 // String formats the address as a valid RFC 5322 address.
140 // If the address's name contains non-ASCII characters
141 // the name will be rendered according to RFC 2047.
142 func (a *Address) String() string {
143         s := "<" + a.Address + ">"
144         if a.Name == "" {
145                 return s
146         }
147         // If every character is printable ASCII, quoting is simple.
148         allPrintable := true
149         for i := 0; i < len(a.Name); i++ {
150                 if !isVchar(a.Name[i]) {
151                         allPrintable = false
152                         break
153                 }
154         }
155         if allPrintable {
156                 b := bytes.NewBufferString(`"`)
157                 for i := 0; i < len(a.Name); i++ {
158                         if !isQtext(a.Name[i]) {
159                                 b.WriteByte('\\')
160                         }
161                         b.WriteByte(a.Name[i])
162                 }
163                 b.WriteString(`" `)
164                 b.WriteString(s)
165                 return b.String()
166         }
167
168         // UTF-8 "Q" encoding
169         b := bytes.NewBufferString("=?utf-8?q?")
170         for i := 0; i < len(a.Name); i++ {
171                 switch c := a.Name[i]; {
172                 case c == ' ':
173                         b.WriteByte('_')
174                 case isVchar(c) && c != '=' && c != '?' && c != '_':
175                         b.WriteByte(c)
176                 default:
177                         fmt.Fprintf(b, "=%02X", c)
178                 }
179         }
180         b.WriteString("?= ")
181         b.WriteString(s)
182         return b.String()
183 }
184
185 type addrParser []byte
186
187 func newAddrParser(s string) *addrParser {
188         p := addrParser(s)
189         return &p
190 }
191
192 func (p *addrParser) parseAddressList() ([]*Address, error) {
193         var list []*Address
194         for {
195                 p.skipSpace()
196                 addr, err := p.parseAddress()
197                 if err != nil {
198                         return nil, err
199                 }
200                 list = append(list, addr)
201
202                 p.skipSpace()
203                 if p.empty() {
204                         break
205                 }
206                 if !p.consume(',') {
207                         return nil, errors.New("mail: expected comma")
208                 }
209         }
210         return list, nil
211 }
212
213 // parseAddress parses a single RFC 5322 address at the start of p.
214 func (p *addrParser) parseAddress() (addr *Address, err error) {
215         debug.Printf("parseAddress: %q", *p)
216         p.skipSpace()
217         if p.empty() {
218                 return nil, errors.New("mail: no address")
219         }
220
221         // address = name-addr / addr-spec
222         // TODO(dsymonds): Support parsing group address.
223
224         // addr-spec has a more restricted grammar than name-addr,
225         // so try parsing it first, and fallback to name-addr.
226         // TODO(dsymonds): Is this really correct?
227         spec, err := p.consumeAddrSpec()
228         if err == nil {
229                 return &Address{
230                         Address: spec,
231                 }, err
232         }
233         debug.Printf("parseAddress: not an addr-spec: %v", err)
234         debug.Printf("parseAddress: state is now %q", *p)
235
236         // display-name
237         var displayName string
238         if p.peek() != '<' {
239                 displayName, err = p.consumePhrase()
240                 if err != nil {
241                         return nil, err
242                 }
243         }
244         debug.Printf("parseAddress: displayName=%q", displayName)
245
246         // angle-addr = "<" addr-spec ">"
247         p.skipSpace()
248         if !p.consume('<') {
249                 return nil, errors.New("mail: no angle-addr")
250         }
251         spec, err = p.consumeAddrSpec()
252         if err != nil {
253                 return nil, err
254         }
255         if !p.consume('>') {
256                 return nil, errors.New("mail: unclosed angle-addr")
257         }
258         debug.Printf("parseAddress: spec=%q", spec)
259
260         return &Address{
261                 Name:    displayName,
262                 Address: spec,
263         }, nil
264 }
265
266 // consumeAddrSpec parses a single RFC 5322 addr-spec at the start of p.
267 func (p *addrParser) consumeAddrSpec() (spec string, err error) {
268         debug.Printf("consumeAddrSpec: %q", *p)
269
270         orig := *p
271         defer func() {
272                 if err != nil {
273                         *p = orig
274                 }
275         }()
276
277         // local-part = dot-atom / quoted-string
278         var localPart string
279         p.skipSpace()
280         if p.empty() {
281                 return "", errors.New("mail: no addr-spec")
282         }
283         if p.peek() == '"' {
284                 // quoted-string
285                 debug.Printf("consumeAddrSpec: parsing quoted-string")
286                 localPart, err = p.consumeQuotedString()
287         } else {
288                 // dot-atom
289                 debug.Printf("consumeAddrSpec: parsing dot-atom")
290                 localPart, err = p.consumeAtom(true)
291         }
292         if err != nil {
293                 debug.Printf("consumeAddrSpec: failed: %v", err)
294                 return "", err
295         }
296
297         if !p.consume('@') {
298                 return "", errors.New("mail: missing @ in addr-spec")
299         }
300
301         // domain = dot-atom / domain-literal
302         var domain string
303         p.skipSpace()
304         if p.empty() {
305                 return "", errors.New("mail: no domain in addr-spec")
306         }
307         // TODO(dsymonds): Handle domain-literal
308         domain, err = p.consumeAtom(true)
309         if err != nil {
310                 return "", err
311         }
312
313         return localPart + "@" + domain, nil
314 }
315
316 // consumePhrase parses the RFC 5322 phrase at the start of p.
317 func (p *addrParser) consumePhrase() (phrase string, err error) {
318         debug.Printf("consumePhrase: [%s]", *p)
319         // phrase = 1*word
320         var words []string
321         for {
322                 // word = atom / quoted-string
323                 var word string
324                 p.skipSpace()
325                 if p.empty() {
326                         return "", errors.New("mail: missing phrase")
327                 }
328                 if p.peek() == '"' {
329                         // quoted-string
330                         word, err = p.consumeQuotedString()
331                 } else {
332                         // atom
333                         word, err = p.consumeAtom(false)
334                 }
335
336                 // RFC 2047 encoded-word starts with =?, ends with ?=, and has two other ?s.
337                 if err == nil && strings.HasPrefix(word, "=?") && strings.HasSuffix(word, "?=") && strings.Count(word, "?") == 4 {
338                         word, err = decodeRFC2047Word(word)
339                 }
340
341                 if err != nil {
342                         break
343                 }
344                 debug.Printf("consumePhrase: consumed %q", word)
345                 words = append(words, word)
346         }
347         // Ignore any error if we got at least one word.
348         if err != nil && len(words) == 0 {
349                 debug.Printf("consumePhrase: hit err: %v", err)
350                 return "", errors.New("mail: missing word in phrase")
351         }
352         phrase = strings.Join(words, " ")
353         return phrase, nil
354 }
355
356 // consumeQuotedString parses the quoted string at the start of p.
357 func (p *addrParser) consumeQuotedString() (qs string, err error) {
358         // Assume first byte is '"'.
359         i := 1
360         qsb := make([]byte, 0, 10)
361 Loop:
362         for {
363                 if i >= p.len() {
364                         return "", errors.New("mail: unclosed quoted-string")
365                 }
366                 switch c := (*p)[i]; {
367                 case c == '"':
368                         break Loop
369                 case c == '\\':
370                         if i+1 == p.len() {
371                                 return "", errors.New("mail: unclosed quoted-string")
372                         }
373                         qsb = append(qsb, (*p)[i+1])
374                         i += 2
375                 case isQtext(c), c == ' ' || c == '\t':
376                         // qtext (printable US-ASCII excluding " and \), or
377                         // FWS (almost; we're ignoring CRLF)
378                         qsb = append(qsb, c)
379                         i++
380                 default:
381                         return "", fmt.Errorf("mail: bad character in quoted-string: %q", c)
382                 }
383         }
384         *p = (*p)[i+1:]
385         return string(qsb), nil
386 }
387
388 // consumeAtom parses an RFC 5322 atom at the start of p.
389 // If dot is true, consumeAtom parses an RFC 5322 dot-atom instead.
390 func (p *addrParser) consumeAtom(dot bool) (atom string, err error) {
391         if !isAtext(p.peek(), false) {
392                 return "", errors.New("mail: invalid string")
393         }
394         i := 1
395         for ; i < p.len() && isAtext((*p)[i], dot); i++ {
396         }
397         // TODO(dsymonds): Remove the []byte() conversion here when 6g doesn't need it.
398         atom, *p = string([]byte((*p)[:i])), (*p)[i:]
399         return atom, nil
400 }
401
402 func (p *addrParser) consume(c byte) bool {
403         if p.empty() || p.peek() != c {
404                 return false
405         }
406         *p = (*p)[1:]
407         return true
408 }
409
410 // skipSpace skips the leading space and tab characters.
411 func (p *addrParser) skipSpace() {
412         *p = bytes.TrimLeft(*p, " \t")
413 }
414
415 func (p *addrParser) peek() byte {
416         return (*p)[0]
417 }
418
419 func (p *addrParser) empty() bool {
420         return p.len() == 0
421 }
422
423 func (p *addrParser) len() int {
424         return len(*p)
425 }
426
427 func decodeRFC2047Word(s string) (string, error) {
428         fields := strings.Split(s, "?")
429         if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" {
430                 return "", errors.New("mail: address not RFC 2047 encoded")
431         }
432         charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2])
433         if charset != "iso-8859-1" && charset != "utf-8" {
434                 return "", fmt.Errorf("mail: charset not supported: %q", charset)
435         }
436
437         in := bytes.NewBufferString(fields[3])
438         var r io.Reader
439         switch enc {
440         case "b":
441                 r = base64.NewDecoder(base64.StdEncoding, in)
442         case "q":
443                 r = qDecoder{r: in}
444         default:
445                 return "", fmt.Errorf("mail: RFC 2047 encoding not supported: %q", enc)
446         }
447
448         dec, err := ioutil.ReadAll(r)
449         if err != nil {
450                 return "", err
451         }
452
453         switch charset {
454         case "iso-8859-1":
455                 b := new(bytes.Buffer)
456                 for _, c := range dec {
457                         b.WriteRune(rune(c))
458                 }
459                 return b.String(), nil
460         case "utf-8":
461                 return string(dec), nil
462         }
463         panic("unreachable")
464 }
465
466 type qDecoder struct {
467         r       io.Reader
468         scratch [2]byte
469 }
470
471 func (qd qDecoder) Read(p []byte) (n int, err error) {
472         // This method writes at most one byte into p.
473         if len(p) == 0 {
474                 return 0, nil
475         }
476         if _, err := qd.r.Read(qd.scratch[:1]); err != nil {
477                 return 0, err
478         }
479         switch c := qd.scratch[0]; {
480         case c == '=':
481                 if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil {
482                         return 0, err
483                 }
484                 x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64)
485                 if err != nil {
486                         return 0, fmt.Errorf("mail: invalid RFC 2047 encoding: %q", qd.scratch[:2])
487                 }
488                 p[0] = byte(x)
489         case c == '_':
490                 p[0] = ' '
491         default:
492                 p[0] = c
493         }
494         return 1, nil
495 }
496
497 var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
498         "abcdefghijklmnopqrstuvwxyz" +
499         "0123456789" +
500         "!#$%&'*+-/=?^_`{|}~")
501
502 // isAtext returns true if c is an RFC 5322 atext character.
503 // If dot is true, period is included.
504 func isAtext(c byte, dot bool) bool {
505         if dot && c == '.' {
506                 return true
507         }
508         return bytes.IndexByte(atextChars, c) >= 0
509 }
510
511 // isQtext returns true if c is an RFC 5322 qtest character.
512 func isQtext(c byte) bool {
513         // Printable US-ASCII, excluding backslash or quote.
514         if c == '\\' || c == '"' {
515                 return false
516         }
517         return '!' <= c && c <= '~'
518 }
519
520 // isVchar returns true if c is an RFC 5322 VCHAR character.
521 func isVchar(c byte) bool {
522         // Visible (printing) characters.
523         return '!' <= c && c <= '~'
524 }