mail: format addresseses correctly.

Also remove an obsolete TODO while I'm here.

R=r, rsc
CC=golang-dev
https://golang.org/cl/4588041
This commit is contained in:
David Symonds 2011-06-08 13:32:47 +10:00
parent 6998ea2982
commit 5c32c96f5c
2 changed files with 82 additions and 5 deletions

View File

@ -23,7 +23,6 @@ import (
"log"
"net/textproto"
"os"
"strconv"
"strings"
"time"
)
@ -97,8 +96,6 @@ func parseDate(date string) (*time.Time, os.Error) {
return nil, os.ErrorString("mail: header could not be parsed")
}
// TODO(dsymonds): Parsers for more specific headers such as To, From, etc.
// A Header represents the key-value pairs in a mail message header.
type Header map[string][]string
@ -136,12 +133,50 @@ type Address struct {
Address string // user@domain
}
// 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
}
return "\"" + strconv.Quote(a.Name) + "\" " + s
// If every character is printable ASCII, quoting is simple.
allPrintable := true
for i := 0; i < len(a.Name); i++ {
if !isVchar(a.Name[i]) {
allPrintable = false
break
}
}
if allPrintable {
b := bytes.NewBufferString(`"`)
for i := 0; i < len(a.Name); i++ {
if !isQtext(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
@ -327,7 +362,7 @@ Loop:
}
qsb = append(qsb, (*p)[i+1])
i += 2
case '!' <= c && c <= '~', c == ' ' || c == '\t':
case isQtext(c), c == ' ' || c == '\t':
// qtext (printable US-ASCII excluding " and \), or
// FWS (almost; we're ignoring CRLF)
qsb = append(qsb, c)
@ -392,3 +427,18 @@ func isAtext(c byte, dot bool) bool {
}
return bytes.IndexByte(atextChars, c) >= 0
}
// isQtext returns true if c is an RFC 5322 qtest 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 <= '~'
}

View File

@ -198,3 +198,30 @@ func TestAddressParsing(t *testing.T) {
}
}
}
func TestAddressFormatting(t *testing.T) {
tests := []struct {
addr *Address
exp string
}{
{
&Address{Address: "bob@example.com"},
"<bob@example.com>",
},
{
&Address{Name: "Bob", Address: "bob@example.com"},
`"Bob" <bob@example.com>`,
},
{
// note the ö (o with an umlaut)
&Address{Name: "Böb", Address: "bob@example.com"},
`=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
},
}
for _, test := range tests {
s := test.addr.String()
if s != test.exp {
t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
}
}
}