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