Skip to content

Commit e5eeadb

Browse files
net/mail: properly handle special characters in phrase and obs-phrase
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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 5a61d8d commit e5eeadb

File tree

2 files changed

+46
-24
lines changed

2 files changed

+46
-24
lines changed

src/net/mail/message.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func (a *Address) String() string {
280280
// Add quotes if needed
281281
quoteLocal := false
282282
for i, r := range local {
283-
if isAtext(r, false, false) {
283+
if isAtext(r, false) {
284284
continue
285285
}
286286
if r == '.' {
@@ -444,7 +444,7 @@ func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
444444
if !p.consume('<') {
445445
atext := true
446446
for _, r := range displayName {
447-
if !isAtext(r, true, false) {
447+
if !isAtext(r, true) {
448448
atext = false
449449
break
450450
}
@@ -479,7 +479,9 @@ func (p *addrParser) consumeGroupList() ([]*Address, error) {
479479
// handle empty group.
480480
p.skipSpace()
481481
if p.consume(';') {
482-
p.skipCFWS()
482+
if !p.skipCFWS() {
483+
return nil, errors.New("mail: misformatted parenthetical comment")
484+
}
483485
return group, nil
484486
}
485487

@@ -496,7 +498,9 @@ func (p *addrParser) consumeGroupList() ([]*Address, error) {
496498
return nil, errors.New("mail: misformatted parenthetical comment")
497499
}
498500
if p.consume(';') {
499-
p.skipCFWS()
501+
if !p.skipCFWS() {
502+
return nil, errors.New("mail: misformatted parenthetical comment")
503+
}
500504
break
501505
}
502506
if !p.consume(',') {
@@ -566,6 +570,12 @@ func (p *addrParser) consumePhrase() (phrase string, err error) {
566570
var words []string
567571
var isPrevEncoded bool
568572
for {
573+
// obs-phrase allows CFWS after one word
574+
if len(words) > 0 {
575+
if !p.skipCFWS() {
576+
return "", errors.New("mail: misformatted parenthetical comment")
577+
}
578+
}
569579
// word = atom / quoted-string
570580
var word string
571581
p.skipSpace()
@@ -661,7 +671,6 @@ Loop:
661671
// If dot is true, consumeAtom parses an RFC 5322 dot-atom instead.
662672
// If permissive is true, consumeAtom will not fail on:
663673
// - leading/trailing/double dots in the atom (see golang.org/issue/4938)
664-
// - special characters (RFC 5322 3.2.3) except '<', '>', ':' and '"' (see golang.org/issue/21018)
665674
func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) {
666675
i := 0
667676

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

675-
case size == 0 || !isAtext(r, dot, permissive):
684+
case size == 0 || !isAtext(r, dot):
676685
break Loop
677686

678687
default:
@@ -850,18 +859,13 @@ func (e charsetError) Error() string {
850859

851860
// isAtext reports whether r is an RFC 5322 atext character.
852861
// If dot is true, period is included.
853-
// If permissive is true, RFC 5322 3.2.3 specials is included,
854-
// except '<', '>', ':' and '"'.
855-
func isAtext(r rune, dot, permissive bool) bool {
862+
func isAtext(r rune, dot bool) bool {
856863
switch r {
857864
case '.':
858865
return dot
859866

860867
// RFC 5322 3.2.3. specials
861-
case '(', ')', '[', ']', ';', '@', '\\', ',':
862-
return permissive
863-
864-
case '<', '>', '"', ':':
868+
case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '"': // RFC 5322 3.2.3. specials
865869
return false
866870
}
867871
return isVchar(r)

src/net/mail/message_test.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,11 @@ func TestAddressParsingError(t *testing.T) {
385385
13: {"group not closed: [email protected]", "expected comma"},
386386
14: {"group: [email protected], [email protected];", "group with multiple addresses"},
387387
15: {"john.doe", "missing '@' or angle-addr"},
388-
16: {"john.doe@", "no angle-addr"},
388+
16: {"john.doe@", "missing '@' or angle-addr"},
389389
17: {"John [email protected]", "no angle-addr"},
390+
18: {" group: [email protected]; (asd", "misformatted parenthetical comment"},
391+
19: {" group: ; (asd", "misformatted parenthetical comment"},
392+
20: {`(John) Doe <[email protected]>`, "missing word in phrase:"},
390393
}
391394

392395
for i, tc := range mustErrTestCases {
@@ -436,24 +439,19 @@ func TestAddressParsing(t *testing.T) {
436439
Address: "[email protected]",
437440
}},
438441
},
439-
{
440-
`"John (middle) Doe" <[email protected]>`,
441-
[]*Address{{
442-
Name: "John (middle) Doe",
443-
Address: "[email protected]",
444-
}},
445-
},
442+
// Comment in display name
446443
{
447444
`John (middle) Doe <[email protected]>`,
448445
[]*Address{{
449-
Name: "John (middle) Doe",
446+
Name: "John Doe",
450447
Address: "[email protected]",
451448
}},
452449
},
450+
// Display name is quoted string, so comment is not a comment
453451
{
454-
`John !@M@! Doe <[email protected]>`,
452+
`"John (middle) Doe" <[email protected]>`,
455453
[]*Address{{
456-
Name: "John !@M@! Doe",
454+
Name: "John (middle) Doe",
457455
Address: "[email protected]",
458456
}},
459457
},
@@ -788,6 +786,26 @@ func TestAddressParsing(t *testing.T) {
788786
},
789787
},
790788
},
789+
// Comment in group display name
790+
{
791+
`group (comment:): [email protected], [email protected];`,
792+
[]*Address{
793+
{
794+
Address: "[email protected]",
795+
},
796+
{
797+
Address: "[email protected]",
798+
},
799+
},
800+
},
801+
{
802+
`x(:"):"@a.example;("@b.example;`,
803+
[]*Address{
804+
{
805+
Address: `@a.example;(@b.example`,
806+
},
807+
},
808+
},
791809
}
792810
for _, test := range tests {
793811
if len(test.exp) == 1 {

0 commit comments

Comments
 (0)