diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
commit | 28592ee1ea1f5cdffcf85472f9de0285d928cf12 (patch) | |
tree | 32944e18b23f7fe4a0818a694aa2a6dfb1835463 /src/pkg/mail/message.go | |
parent | e836bee4716dc0d4d913537ad3ad1925a7ac32d0 (diff) | |
download | golang-upstream/59.tar.gz |
Imported Upstream version 59upstream/59
Diffstat (limited to 'src/pkg/mail/message.go')
-rw-r--r-- | src/pkg/mail/message.go | 115 |
1 files changed, 74 insertions, 41 deletions
diff --git a/src/pkg/mail/message.go b/src/pkg/mail/message.go index 754b779be..e227d17d6 100644 --- a/src/pkg/mail/message.go +++ b/src/pkg/mail/message.go @@ -18,8 +18,10 @@ package mail import ( "bufio" "bytes" + "encoding/base64" "fmt" "io" + "io/ioutil" "log" "net/textproto" "os" @@ -94,7 +96,7 @@ func parseDate(date string) (*time.Time, os.Error) { return t, nil } } - return nil, os.ErrorString("mail: header could not be parsed") + return nil, os.NewError("mail: header could not be parsed") } // A Header represents the key-value pairs in a mail message header. @@ -106,7 +108,7 @@ func (h Header) Get(key string) string { return textproto.MIMEHeader(h).Get(key) } -var ErrHeaderNotPresent = os.ErrorString("mail: header not in message") +var ErrHeaderNotPresent = os.NewError("mail: header not in message") // Date parses the Date header field. func (h Header) Date() (*time.Time, os.Error) { @@ -202,7 +204,7 @@ func (p *addrParser) parseAddressList() ([]*Address, os.Error) { break } if !p.consume(',') { - return nil, os.ErrorString("mail: expected comma") + return nil, os.NewError("mail: expected comma") } } return list, nil @@ -213,7 +215,7 @@ func (p *addrParser) parseAddress() (addr *Address, err os.Error) { debug.Printf("parseAddress: %q", *p) p.skipSpace() if p.empty() { - return nil, os.ErrorString("mail: no address") + return nil, os.NewError("mail: no address") } // address = name-addr / addr-spec @@ -244,14 +246,14 @@ func (p *addrParser) parseAddress() (addr *Address, err os.Error) { // angle-addr = "<" addr-spec ">" p.skipSpace() if !p.consume('<') { - return nil, os.ErrorString("mail: no angle-addr") + return nil, os.NewError("mail: no angle-addr") } spec, err = p.consumeAddrSpec() if err != nil { return nil, err } if !p.consume('>') { - return nil, os.ErrorString("mail: unclosed angle-addr") + return nil, os.NewError("mail: unclosed angle-addr") } debug.Printf("parseAddress: spec=%q", spec) @@ -276,7 +278,7 @@ func (p *addrParser) consumeAddrSpec() (spec string, err os.Error) { var localPart string p.skipSpace() if p.empty() { - return "", os.ErrorString("mail: no addr-spec") + return "", os.NewError("mail: no addr-spec") } if p.peek() == '"' { // quoted-string @@ -293,14 +295,14 @@ func (p *addrParser) consumeAddrSpec() (spec string, err os.Error) { } if !p.consume('@') { - return "", os.ErrorString("mail: missing @ in addr-spec") + return "", os.NewError("mail: missing @ in addr-spec") } // domain = dot-atom / domain-literal var domain string p.skipSpace() if p.empty() { - return "", os.ErrorString("mail: no domain in addr-spec") + return "", os.NewError("mail: no domain in addr-spec") } // TODO(dsymonds): Handle domain-literal domain, err = p.consumeAtom(true) @@ -321,7 +323,7 @@ func (p *addrParser) consumePhrase() (phrase string, err os.Error) { var word string p.skipSpace() if p.empty() { - return "", os.ErrorString("mail: missing phrase") + return "", os.NewError("mail: missing phrase") } if p.peek() == '"' { // quoted-string @@ -345,7 +347,7 @@ func (p *addrParser) consumePhrase() (phrase string, err os.Error) { // Ignore any error if we got at least one word. if err != nil && len(words) == 0 { debug.Printf("consumePhrase: hit err: %v", err) - return "", os.ErrorString("mail: missing word in phrase") + return "", os.NewError("mail: missing word in phrase") } phrase = strings.Join(words, " ") return phrase, nil @@ -359,14 +361,14 @@ func (p *addrParser) consumeQuotedString() (qs string, err os.Error) { Loop: for { if i >= p.len() { - return "", os.ErrorString("mail: unclosed quoted-string") + return "", os.NewError("mail: unclosed quoted-string") } switch c := (*p)[i]; { case c == '"': break Loop case c == '\\': if i+1 == p.len() { - return "", os.ErrorString("mail: unclosed quoted-string") + return "", os.NewError("mail: unclosed quoted-string") } qsb = append(qsb, (*p)[i+1]) i += 2 @@ -387,7 +389,7 @@ Loop: // If dot is true, consumeAtom parses an RFC 5322 dot-atom instead. func (p *addrParser) consumeAtom(dot bool) (atom string, err os.Error) { if !isAtext(p.peek(), false) { - return "", os.ErrorString("mail: invalid string") + return "", os.NewError("mail: invalid string") } i := 1 for ; i < p.len() && isAtext((*p)[i], dot); i++ { @@ -423,42 +425,73 @@ func (p *addrParser) len() int { } func decodeRFC2047Word(s string) (string, os.Error) { - fields := strings.Split(s, "?", -1) + fields := strings.Split(s, "?") if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" { - return "", os.ErrorString("mail: address not RFC 2047 encoded") + return "", os.NewError("mail: address not RFC 2047 encoded") } charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) - // TODO(dsymonds): Support "b" encoding too. - if enc != "q" { - return "", fmt.Errorf("mail: RFC 2047 encoding not supported: %q", enc) - } if charset != "iso-8859-1" && charset != "utf-8" { return "", fmt.Errorf("mail: charset not supported: %q", charset) } - in := fields[3] - b := new(bytes.Buffer) - for i := 0; i < len(in); i++ { - switch c := in[i]; { - case c == '=' && i+2 < len(in): - x, err := strconv.Btoi64(in[i+1:i+3], 16) - if err != nil { - return "", fmt.Errorf("mail: invalid RFC 2047 encoding: %q", in[i:i+3]) - } - i += 2 - switch charset { - case "iso-8859-1": - b.WriteRune(int(x)) - case "utf-8": - b.WriteByte(byte(x)) - } - case c == '_': - b.WriteByte(' ') - default: - b.WriteByte(c) + 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("mail: 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(int(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 os.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.Btoi64(string(qd.scratch[:2]), 16) + 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 b.String(), nil + return 1, nil } var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + |