Commit 371aa14e authored by David Symonds's avatar David Symonds

mail: decode RFC 2047 "B" encoding.

R=rsc, r
CC=golang-dev
https://golang.org/cl/4584056
parent c6c8dbd6
...@@ -18,8 +18,10 @@ package mail ...@@ -18,8 +18,10 @@ package mail
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/base64"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/textproto" "net/textproto"
"os" "os"
...@@ -57,7 +59,7 @@ func ReadMessage(r io.Reader) (msg *Message, err os.Error) { ...@@ -57,7 +59,7 @@ func ReadMessage(r io.Reader) (msg *Message, err os.Error) {
return &Message{ return &Message{
Header: Header(hdr), Header: Header(hdr),
Body: tp.R, Body: tp.R,
}, nil },nil
} }
// Layouts suitable for passing to time.Parse. // Layouts suitable for passing to time.Parse.
...@@ -226,7 +228,7 @@ func (p *addrParser) parseAddress() (addr *Address, err os.Error) { ...@@ -226,7 +228,7 @@ func (p *addrParser) parseAddress() (addr *Address, err os.Error) {
if err == nil { if err == nil {
return &Address{ return &Address{
Address: spec, Address: spec,
}, err },err
} }
debug.Printf("parseAddress: not an addr-spec: %v", err) debug.Printf("parseAddress: not an addr-spec: %v", err)
debug.Printf("parseAddress: state is now %q", *p) debug.Printf("parseAddress: state is now %q", *p)
...@@ -258,7 +260,7 @@ func (p *addrParser) parseAddress() (addr *Address, err os.Error) { ...@@ -258,7 +260,7 @@ func (p *addrParser) parseAddress() (addr *Address, err os.Error) {
return &Address{ return &Address{
Name: displayName, Name: displayName,
Address: spec, Address: spec,
}, nil },nil
} }
// consumeAddrSpec parses a single RFC 5322 addr-spec at the start of p. // consumeAddrSpec parses a single RFC 5322 addr-spec at the start of p.
...@@ -428,37 +430,68 @@ func decodeRFC2047Word(s string) (string, os.Error) { ...@@ -428,37 +430,68 @@ func decodeRFC2047Word(s string) (string, os.Error) {
return "", os.ErrorString("mail: address not RFC 2047 encoded") return "", os.ErrorString("mail: address not RFC 2047 encoded")
} }
charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) 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" { if charset != "iso-8859-1" && charset != "utf-8" {
return "", fmt.Errorf("mail: charset not supported: %q", charset) return "", fmt.Errorf("mail: charset not supported: %q", charset)
} }
in := fields[3] in := bytes.NewBufferString(fields[3])
b := new(bytes.Buffer) var r io.Reader
for i := 0; i < len(in); i++ { switch enc {
switch c := in[i]; { case "b":
case c == '=' && i+2 < len(in): r = base64.NewDecoder(base64.StdEncoding, in)
x, err := strconv.Btoi64(in[i+1:i+3], 16) case "q":
if err != nil { r = qDecoder{r: in}
return "", fmt.Errorf("mail: invalid RFC 2047 encoding: %q", in[i:i+3]) default:
} return "", fmt.Errorf("mail: RFC 2047 encoding not supported: %q", enc)
i += 2 }
switch charset {
case "iso-8859-1": dec, err := ioutil.ReadAll(r)
b.WriteRune(int(x)) if err != nil {
case "utf-8": return "", err
b.WriteByte(byte(x)) }
}
case c == '_': switch charset {
b.WriteByte(' ') case "iso-8859-1":
default: b := new(bytes.Buffer)
b.WriteByte(c) 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" + var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
......
...@@ -217,6 +217,27 @@ func TestAddressParsing(t *testing.T) { ...@@ -217,6 +217,27 @@ func TestAddressParsing(t *testing.T) {
}, },
}, },
}, },
// Custom example of RFC 2047 "B"-encoded ISO-8859-1 address.
{
`=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
[]*Address{
&Address{
Name: `Jörg`,
Address: "joerg@example.com",
},
},
},
// Custom example of RFC 2047 "B"-encoded UTF-8 address.
{
// XXX: a different example
`=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
[]*Address{
&Address{
Name: `Jörg`,
Address: "joerg@example.com",
},
},
},
} }
for _, test := range tests { for _, test := range tests {
addrs, err := newAddrParser(test.addrsStr).parseAddressList() addrs, err := newAddrParser(test.addrsStr).parseAddressList()
...@@ -225,7 +246,7 @@ func TestAddressParsing(t *testing.T) { ...@@ -225,7 +246,7 @@ func TestAddressParsing(t *testing.T) {
continue continue
} }
if !reflect.DeepEqual(addrs, test.exp) { if !reflect.DeepEqual(addrs, test.exp) {
t.Errorf("Parse of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) t.Errorf("Parse of %q: got %+v, want %+v", test.addrsStr, *addrs[0], *test.exp[0])
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment