Skip to content

Commit

Permalink
net/mail: properly handle special characters in phrase and obs-phrase
Browse files Browse the repository at this point in the history
Fixes a couple of misalignments with RFC 5322 which introduce
significant diffs between (mostly) conformant parsers.

This change reverts the changes made in CL50911, which allowed certain
special RFC 5322 characters to appear unquoted in the "phrase" syntax.
It is unclear why this change was made in the first place, and created
a divergence from comformant parsers. In particular this resulted in
treating comments in display names incorrectly.

Additionally properly handle trailing malformed comments in the group
syntax.

Fixes #65083

Change-Id: I00dddc044c6ae3381154e43236632604c390f672
Reviewed-on: https://go-review.googlesource.com/c/go/+/555596
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
rolandshoemaker committed Jan 22, 2024
1 parent 5a61d8d commit e5eeadb
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 24 deletions.
30 changes: 17 additions & 13 deletions src/net/mail/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func (a *Address) String() string {
// Add quotes if needed
quoteLocal := false
for i, r := range local {
if isAtext(r, false, false) {
if isAtext(r, false) {
continue
}
if r == '.' {
Expand Down Expand Up @@ -444,7 +444,7 @@ func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
if !p.consume('<') {
atext := true
for _, r := range displayName {
if !isAtext(r, true, false) {
if !isAtext(r, true) {
atext = false
break
}
Expand Down Expand Up @@ -479,7 +479,9 @@ func (p *addrParser) consumeGroupList() ([]*Address, error) {
// handle empty group.
p.skipSpace()
if p.consume(';') {
p.skipCFWS()
if !p.skipCFWS() {
return nil, errors.New("mail: misformatted parenthetical comment")
}
return group, nil
}

Expand All @@ -496,7 +498,9 @@ func (p *addrParser) consumeGroupList() ([]*Address, error) {
return nil, errors.New("mail: misformatted parenthetical comment")
}
if p.consume(';') {
p.skipCFWS()
if !p.skipCFWS() {
return nil, errors.New("mail: misformatted parenthetical comment")
}
break
}
if !p.consume(',') {
Expand Down Expand Up @@ -566,6 +570,12 @@ func (p *addrParser) consumePhrase() (phrase string, err error) {
var words []string
var isPrevEncoded bool
for {
// obs-phrase allows CFWS after one word
if len(words) > 0 {
if !p.skipCFWS() {
return "", errors.New("mail: misformatted parenthetical comment")
}
}
// word = atom / quoted-string
var word string
p.skipSpace()
Expand Down Expand Up @@ -661,7 +671,6 @@ Loop:
// If dot is true, consumeAtom parses an RFC 5322 dot-atom instead.
// If permissive is true, consumeAtom will not fail on:
// - leading/trailing/double dots in the atom (see golang.org/issue/4938)
// - special characters (RFC 5322 3.2.3) except '<', '>', ':' and '"' (see golang.org/issue/21018)
func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) {
i := 0

Expand All @@ -672,7 +681,7 @@ Loop:
case size == 1 && r == utf8.RuneError:
return "", fmt.Errorf("mail: invalid utf-8 in address: %q", p.s)

case size == 0 || !isAtext(r, dot, permissive):
case size == 0 || !isAtext(r, dot):
break Loop

default:
Expand Down Expand Up @@ -850,18 +859,13 @@ func (e charsetError) Error() string {

// isAtext reports whether r is an RFC 5322 atext character.
// If dot is true, period is included.
// If permissive is true, RFC 5322 3.2.3 specials is included,
// except '<', '>', ':' and '"'.
func isAtext(r rune, dot, permissive bool) bool {
func isAtext(r rune, dot bool) bool {
switch r {
case '.':
return dot

// RFC 5322 3.2.3. specials
case '(', ')', '[', ']', ';', '@', '\\', ',':
return permissive

case '<', '>', '"', ':':
case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '"': // RFC 5322 3.2.3. specials
return false
}
return isVchar(r)
Expand Down
40 changes: 29 additions & 11 deletions src/net/mail/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,11 @@ func TestAddressParsingError(t *testing.T) {
13: {"group not closed: null@example.com", "expected comma"},
14: {"group: first@example.com, second@example.com;", "group with multiple addresses"},
15: {"john.doe", "missing '@' or angle-addr"},
16: {"john.doe@", "no angle-addr"},
16: {"john.doe@", "missing '@' or angle-addr"},
17: {"John Doe@foo.bar", "no angle-addr"},
18: {" group: null@example.com; (asd", "misformatted parenthetical comment"},
19: {" group: ; (asd", "misformatted parenthetical comment"},
20: {`(John) Doe <jdoe@machine.example>`, "missing word in phrase:"},
}

for i, tc := range mustErrTestCases {
Expand Down Expand Up @@ -436,24 +439,19 @@ func TestAddressParsing(t *testing.T) {
Address: "john.q.public@example.com",
}},
},
{
`"John (middle) Doe" <jdoe@machine.example>`,
[]*Address{{
Name: "John (middle) Doe",
Address: "jdoe@machine.example",
}},
},
// Comment in display name
{
`John (middle) Doe <jdoe@machine.example>`,
[]*Address{{
Name: "John (middle) Doe",
Name: "John Doe",
Address: "jdoe@machine.example",
}},
},
// Display name is quoted string, so comment is not a comment
{
`John !@M@! Doe <jdoe@machine.example>`,
`"John (middle) Doe" <jdoe@machine.example>`,
[]*Address{{
Name: "John !@M@! Doe",
Name: "John (middle) Doe",
Address: "jdoe@machine.example",
}},
},
Expand Down Expand Up @@ -788,6 +786,26 @@ func TestAddressParsing(t *testing.T) {
},
},
},
// Comment in group display name
{
`group (comment:): a@example.com, b@example.com;`,
[]*Address{
{
Address: "a@example.com",
},
{
Address: "b@example.com",
},
},
},
{
`x(:"):"@a.example;("@b.example;`,
[]*Address{
{
Address: `@a.example;(@b.example`,
},
},
},
}
for _, test := range tests {
if len(test.exp) == 1 {
Expand Down

0 comments on commit e5eeadb

Please sign in to comment.