diff options
Diffstat (limited to 'src/pkg/net/mail/message.go')
-rw-r--r-- | src/pkg/net/mail/message.go | 545 |
1 files changed, 0 insertions, 545 deletions
diff --git a/src/pkg/net/mail/message.go b/src/pkg/net/mail/message.go deleted file mode 100644 index ba0778caa..000000000 --- a/src/pkg/net/mail/message.go +++ /dev/null @@ -1,545 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package mail implements parsing of mail messages. - -For the most part, this package follows the syntax as specified by RFC 5322. -Notable divergences: - * Obsolete address formats are not parsed, including addresses with - embedded route information. - * Group addresses are not parsed. - * The full range of spacing (the CFWS syntax element) is not supported, - such as breaking addresses across lines. -*/ -package mail - -import ( - "bufio" - "bytes" - "encoding/base64" - "errors" - "fmt" - "io" - "io/ioutil" - "log" - "net/textproto" - "strconv" - "strings" - "time" -) - -var debug = debugT(false) - -type debugT bool - -func (d debugT) Printf(format string, args ...interface{}) { - if d { - log.Printf(format, args...) - } -} - -// A Message represents a parsed mail message. -type Message struct { - Header Header - Body io.Reader -} - -// ReadMessage reads a message from r. -// The headers are parsed, and the body of the message will be available -// for reading from r. -func ReadMessage(r io.Reader) (msg *Message, err error) { - tp := textproto.NewReader(bufio.NewReader(r)) - - hdr, err := tp.ReadMIMEHeader() - if err != nil { - return nil, err - } - - return &Message{ - Header: Header(hdr), - Body: tp.R, - }, nil -} - -// Layouts suitable for passing to time.Parse. -// These are tried in order. -var dateLayouts []string - -func init() { - // Generate layouts based on RFC 5322, section 3.3. - - dows := [...]string{"", "Mon, "} // day-of-week - days := [...]string{"2", "02"} // day = 1*2DIGIT - years := [...]string{"2006", "06"} // year = 4*DIGIT / 2*DIGIT - seconds := [...]string{":05", ""} // second - // "-0700 (MST)" is not in RFC 5322, but is common. - zones := [...]string{"-0700", "MST", "-0700 (MST)"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ... - - for _, dow := range dows { - for _, day := range days { - for _, year := range years { - for _, second := range seconds { - for _, zone := range zones { - s := dow + day + " Jan " + year + " 15:04" + second + " " + zone - dateLayouts = append(dateLayouts, s) - } - } - } - } - } -} - -func parseDate(date string) (time.Time, error) { - for _, layout := range dateLayouts { - t, err := time.Parse(layout, date) - if err == nil { - return t, nil - } - } - return time.Time{}, errors.New("mail: header could not be parsed") -} - -// A Header represents the key-value pairs in a mail message header. -type Header map[string][]string - -// Get gets the first value associated with the given key. -// If there are no values associated with the key, Get returns "". -func (h Header) Get(key string) string { - return textproto.MIMEHeader(h).Get(key) -} - -var ErrHeaderNotPresent = errors.New("mail: header not in message") - -// Date parses the Date header field. -func (h Header) Date() (time.Time, error) { - hdr := h.Get("Date") - if hdr == "" { - return time.Time{}, ErrHeaderNotPresent - } - return parseDate(hdr) -} - -// AddressList parses the named header field as a list of addresses. -func (h Header) AddressList(key string) ([]*Address, error) { - hdr := h.Get(key) - if hdr == "" { - return nil, ErrHeaderNotPresent - } - return ParseAddressList(hdr) -} - -// Address represents a single mail address. -// An address such as "Barry Gibbs <bg@example.com>" is represented -// as Address{Name: "Barry Gibbs", Address: "bg@example.com"}. -type Address struct { - Name string // Proper name; may be empty. - Address string // user@domain -} - -// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>" -func ParseAddress(address string) (*Address, error) { - return newAddrParser(address).parseAddress() -} - -// ParseAddressList parses the given string as a list of addresses. -func ParseAddressList(list string) ([]*Address, error) { - return newAddrParser(list).parseAddressList() -} - -// String formats the address as a valid RFC 5322 address. -// If the address's name contains non-ASCII characters -// the name will be rendered according to RFC 2047. -func (a *Address) String() string { - s := "<" + a.Address + ">" - if a.Name == "" { - return s - } - // If every character is printable ASCII, quoting is simple. - allPrintable := true - for i := 0; i < len(a.Name); i++ { - // isWSP here should actually be isFWS, - // but we don't support folding yet. - if !isVchar(a.Name[i]) && !isWSP(a.Name[i]) { - allPrintable = false - break - } - } - if allPrintable { - b := bytes.NewBufferString(`"`) - for i := 0; i < len(a.Name); i++ { - if !isQtext(a.Name[i]) && !isWSP(a.Name[i]) { - b.WriteByte('\\') - } - b.WriteByte(a.Name[i]) - } - b.WriteString(`" `) - b.WriteString(s) - return b.String() - } - - // UTF-8 "Q" encoding - b := bytes.NewBufferString("=?utf-8?q?") - for i := 0; i < len(a.Name); i++ { - switch c := a.Name[i]; { - case c == ' ': - b.WriteByte('_') - case isVchar(c) && c != '=' && c != '?' && c != '_': - b.WriteByte(c) - default: - fmt.Fprintf(b, "=%02X", c) - } - } - b.WriteString("?= ") - b.WriteString(s) - return b.String() -} - -type addrParser []byte - -func newAddrParser(s string) *addrParser { - p := addrParser(s) - return &p -} - -func (p *addrParser) parseAddressList() ([]*Address, error) { - var list []*Address - for { - p.skipSpace() - addr, err := p.parseAddress() - if err != nil { - return nil, err - } - list = append(list, addr) - - p.skipSpace() - if p.empty() { - break - } - if !p.consume(',') { - return nil, errors.New("mail: expected comma") - } - } - return list, nil -} - -// parseAddress parses a single RFC 5322 address at the start of p. -func (p *addrParser) parseAddress() (addr *Address, err error) { - debug.Printf("parseAddress: %q", *p) - p.skipSpace() - if p.empty() { - return nil, errors.New("mail: no address") - } - - // address = name-addr / addr-spec - // TODO(dsymonds): Support parsing group address. - - // addr-spec has a more restricted grammar than name-addr, - // so try parsing it first, and fallback to name-addr. - // TODO(dsymonds): Is this really correct? - spec, err := p.consumeAddrSpec() - if err == nil { - return &Address{ - Address: spec, - }, err - } - debug.Printf("parseAddress: not an addr-spec: %v", err) - debug.Printf("parseAddress: state is now %q", *p) - - // display-name - var displayName string - if p.peek() != '<' { - displayName, err = p.consumePhrase() - if err != nil { - return nil, err - } - } - debug.Printf("parseAddress: displayName=%q", displayName) - - // angle-addr = "<" addr-spec ">" - p.skipSpace() - if !p.consume('<') { - return nil, errors.New("mail: no angle-addr") - } - spec, err = p.consumeAddrSpec() - if err != nil { - return nil, err - } - if !p.consume('>') { - return nil, errors.New("mail: unclosed angle-addr") - } - debug.Printf("parseAddress: spec=%q", spec) - - return &Address{ - Name: displayName, - Address: spec, - }, nil -} - -// consumeAddrSpec parses a single RFC 5322 addr-spec at the start of p. -func (p *addrParser) consumeAddrSpec() (spec string, err error) { - debug.Printf("consumeAddrSpec: %q", *p) - - orig := *p - defer func() { - if err != nil { - *p = orig - } - }() - - // local-part = dot-atom / quoted-string - var localPart string - p.skipSpace() - if p.empty() { - return "", errors.New("mail: no addr-spec") - } - if p.peek() == '"' { - // quoted-string - debug.Printf("consumeAddrSpec: parsing quoted-string") - localPart, err = p.consumeQuotedString() - } else { - // dot-atom - debug.Printf("consumeAddrSpec: parsing dot-atom") - localPart, err = p.consumeAtom(true) - } - if err != nil { - debug.Printf("consumeAddrSpec: failed: %v", err) - return "", err - } - - if !p.consume('@') { - return "", errors.New("mail: missing @ in addr-spec") - } - - // domain = dot-atom / domain-literal - var domain string - p.skipSpace() - if p.empty() { - return "", errors.New("mail: no domain in addr-spec") - } - // TODO(dsymonds): Handle domain-literal - domain, err = p.consumeAtom(true) - if err != nil { - return "", err - } - - return localPart + "@" + domain, nil -} - -// consumePhrase parses the RFC 5322 phrase at the start of p. -func (p *addrParser) consumePhrase() (phrase string, err error) { - debug.Printf("consumePhrase: [%s]", *p) - // phrase = 1*word - var words []string - for { - // word = atom / quoted-string - var word string - p.skipSpace() - if p.empty() { - return "", errors.New("mail: missing phrase") - } - if p.peek() == '"' { - // quoted-string - word, err = p.consumeQuotedString() - } else { - // atom - // We actually parse dot-atom here to be more permissive - // than what RFC 5322 specifies. - word, err = p.consumeAtom(true) - } - - // RFC 2047 encoded-word starts with =?, ends with ?=, and has two other ?s. - if err == nil && strings.HasPrefix(word, "=?") && strings.HasSuffix(word, "?=") && strings.Count(word, "?") == 4 { - word, err = decodeRFC2047Word(word) - } - - if err != nil { - break - } - debug.Printf("consumePhrase: consumed %q", word) - words = append(words, word) - } - // Ignore any error if we got at least one word. - if err != nil && len(words) == 0 { - debug.Printf("consumePhrase: hit err: %v", err) - return "", fmt.Errorf("mail: missing word in phrase: %v", err) - } - phrase = strings.Join(words, " ") - return phrase, nil -} - -// consumeQuotedString parses the quoted string at the start of p. -func (p *addrParser) consumeQuotedString() (qs string, err error) { - // Assume first byte is '"'. - i := 1 - qsb := make([]byte, 0, 10) -Loop: - for { - if i >= p.len() { - return "", errors.New("mail: unclosed quoted-string") - } - switch c := (*p)[i]; { - case c == '"': - break Loop - case c == '\\': - if i+1 == p.len() { - return "", errors.New("mail: unclosed quoted-string") - } - qsb = append(qsb, (*p)[i+1]) - i += 2 - case isQtext(c), c == ' ' || c == '\t': - // qtext (printable US-ASCII excluding " and \), or - // FWS (almost; we're ignoring CRLF) - qsb = append(qsb, c) - i++ - default: - return "", fmt.Errorf("mail: bad character in quoted-string: %q", c) - } - } - *p = (*p)[i+1:] - return string(qsb), nil -} - -// consumeAtom parses an RFC 5322 atom at the start of p. -// If dot is true, consumeAtom parses an RFC 5322 dot-atom instead. -func (p *addrParser) consumeAtom(dot bool) (atom string, err error) { - if !isAtext(p.peek(), false) { - return "", errors.New("mail: invalid string") - } - i := 1 - for ; i < p.len() && isAtext((*p)[i], dot); i++ { - } - atom, *p = string((*p)[:i]), (*p)[i:] - return atom, nil -} - -func (p *addrParser) consume(c byte) bool { - if p.empty() || p.peek() != c { - return false - } - *p = (*p)[1:] - return true -} - -// skipSpace skips the leading space and tab characters. -func (p *addrParser) skipSpace() { - *p = bytes.TrimLeft(*p, " \t") -} - -func (p *addrParser) peek() byte { - return (*p)[0] -} - -func (p *addrParser) empty() bool { - return p.len() == 0 -} - -func (p *addrParser) len() int { - return len(*p) -} - -func decodeRFC2047Word(s string) (string, error) { - fields := strings.Split(s, "?") - if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" { - return "", errors.New("address not RFC 2047 encoded") - } - charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) - if charset != "iso-8859-1" && charset != "utf-8" { - return "", fmt.Errorf("charset not supported: %q", charset) - } - - in := bytes.NewBufferString(fields[3]) - var r io.Reader - switch enc { - case "b": - r = base64.NewDecoder(base64.StdEncoding, in) - case "q": - r = qDecoder{r: in} - default: - return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc) - } - - dec, err := ioutil.ReadAll(r) - if err != nil { - return "", err - } - - switch charset { - case "iso-8859-1": - b := new(bytes.Buffer) - for _, c := range dec { - b.WriteRune(rune(c)) - } - return b.String(), nil - case "utf-8": - return string(dec), nil - } - panic("unreachable") -} - -type qDecoder struct { - r io.Reader - scratch [2]byte -} - -func (qd qDecoder) Read(p []byte) (n int, err error) { - // This method writes at most one byte into p. - if len(p) == 0 { - return 0, nil - } - if _, err := qd.r.Read(qd.scratch[:1]); err != nil { - return 0, err - } - switch c := qd.scratch[0]; { - case c == '=': - if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil { - return 0, err - } - x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64) - if err != nil { - return 0, fmt.Errorf("mail: invalid RFC 2047 encoding: %q", qd.scratch[:2]) - } - p[0] = byte(x) - case c == '_': - p[0] = ' ' - default: - p[0] = c - } - return 1, nil -} - -var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "abcdefghijklmnopqrstuvwxyz" + - "0123456789" + - "!#$%&'*+-/=?^_`{|}~") - -// isAtext returns true if c is an RFC 5322 atext character. -// If dot is true, period is included. -func isAtext(c byte, dot bool) bool { - if dot && c == '.' { - return true - } - return bytes.IndexByte(atextChars, c) >= 0 -} - -// isQtext returns true if c is an RFC 5322 qtext character. -func isQtext(c byte) bool { - // Printable US-ASCII, excluding backslash or quote. - if c == '\\' || c == '"' { - return false - } - return '!' <= c && c <= '~' -} - -// isVchar returns true if c is an RFC 5322 VCHAR character. -func isVchar(c byte) bool { - // Visible (printing) characters. - return '!' <= c && c <= '~' -} - -// isWSP returns true if c is a WSP (white space). -// WSP is a space or horizontal tab (RFC5234 Appendix B). -func isWSP(c byte) bool { - return c == ' ' || c == '\t' -} |