aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Symonds <dsymonds@golang.org>2011-06-08 13:32:47 +1000
committerDavid Symonds <dsymonds@golang.org>2011-06-08 13:32:47 +1000
commit5c32c96f5cd624a4ab4cde4b8e4abd3dbe9be4a6 (patch)
tree141b467996f817127121f48096ee591ecd35cb30
parent6998ea2982ad97db0166cbf5e22bab3087063154 (diff)
downloadgo-5c32c96f5cd624a4ab4cde4b8e4abd3dbe9be4a6.tar.gz
go-5c32c96f5cd624a4ab4cde4b8e4abd3dbe9be4a6.zip
mail: format addresseses correctly.
Also remove an obsolete TODO while I'm here. R=r, rsc CC=golang-dev https://golang.org/cl/4588041
-rw-r--r--src/pkg/mail/message.go60
-rw-r--r--src/pkg/mail/message_test.go27
2 files changed, 82 insertions, 5 deletions
diff --git a/src/pkg/mail/message.go b/src/pkg/mail/message.go
index 50d89d3574..377a8d3943 100644
--- a/src/pkg/mail/message.go
+++ b/src/pkg/mail/message.go
@@ -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 <= '~'
+}
diff --git a/src/pkg/mail/message_test.go b/src/pkg/mail/message_test.go
index c3ec236816..731a748ede 100644
--- a/src/pkg/mail/message_test.go
+++ b/src/pkg/mail/message_test.go
@@ -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)
+ }
+ }
+}