Commit 6f2e5f9c authored by Mihail Minaev's avatar Mihail Minaev Committed by Ian Lance Taylor

net/mail: parse group in email address

This change adds the ability to parse
group into email address. The information about
group name and group members is lost for
backwards compatibility. According to this rule address
`Group: Test <text@example.com>;` would be parsed into
`Test <test@example.com>`.

Fixes #22014

Change-Id: I6e804a62f3ede04f555a1b82500b8ca030eeb431
Reviewed-on: https://go-review.googlesource.com/66250
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent 90fdc455
...@@ -10,7 +10,6 @@ extended by RFC 6532. ...@@ -10,7 +10,6 @@ extended by RFC 6532.
Notable divergences: Notable divergences:
* Obsolete address formats are not parsed, including addresses with * Obsolete address formats are not parsed, including addresses with
embedded route information. embedded route information.
* Group addresses are not parsed.
* The full range of spacing (the CFWS syntax element) is not supported, * The full range of spacing (the CFWS syntax element) is not supported,
such as breaking addresses across lines. such as breaking addresses across lines.
* No unicode normalization is performed. * No unicode normalization is performed.
...@@ -248,11 +247,11 @@ func (p *addrParser) parseAddressList() ([]*Address, error) { ...@@ -248,11 +247,11 @@ func (p *addrParser) parseAddressList() ([]*Address, error) {
var list []*Address var list []*Address
for { for {
p.skipSpace() p.skipSpace()
addr, err := p.parseAddress() addrs, err := p.parseAddress(true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list = append(list, addr) list = append(list, addrs...)
if !p.skipCfws() { if !p.skipCfws() {
return nil, errors.New("mail: misformatted parenthetical comment") return nil, errors.New("mail: misformatted parenthetical comment")
...@@ -268,7 +267,7 @@ func (p *addrParser) parseAddressList() ([]*Address, error) { ...@@ -268,7 +267,7 @@ func (p *addrParser) parseAddressList() ([]*Address, error) {
} }
func (p *addrParser) parseSingleAddress() (*Address, error) { func (p *addrParser) parseSingleAddress() (*Address, error) {
addr, err := p.parseAddress() addrs, err := p.parseAddress(true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -278,28 +277,35 @@ func (p *addrParser) parseSingleAddress() (*Address, error) { ...@@ -278,28 +277,35 @@ func (p *addrParser) parseSingleAddress() (*Address, error) {
if !p.empty() { if !p.empty() {
return nil, fmt.Errorf("mail: expected single address, got %q", p.s) return nil, fmt.Errorf("mail: expected single address, got %q", p.s)
} }
return addr, nil if len(addrs) == 0 {
return nil, errors.New("mail: empty group")
}
if len(addrs) > 1 {
return nil, errors.New("mail: group with multiple addresses")
}
return addrs[0], nil
} }
// parseAddress parses a single RFC 5322 address at the start of p. // parseAddress parses a single RFC 5322 address at the start of p.
func (p *addrParser) parseAddress() (addr *Address, err error) { func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
debug.Printf("parseAddress: %q", p.s) debug.Printf("parseAddress: %q", p.s)
p.skipSpace() p.skipSpace()
if p.empty() { if p.empty() {
return nil, errors.New("mail: no address") return nil, errors.New("mail: no address")
} }
// address = name-addr / addr-spec // address = mailbox / group
// TODO(dsymonds): Support parsing group address. // mailbox = name-addr / addr-spec
// group = display-name ":" [group-list] ";" [CFWS]
// addr-spec has a more restricted grammar than name-addr, // addr-spec has a more restricted grammar than name-addr,
// so try parsing it first, and fallback to name-addr. // so try parsing it first, and fallback to name-addr.
// TODO(dsymonds): Is this really correct? // TODO(dsymonds): Is this really correct?
spec, err := p.consumeAddrSpec() spec, err := p.consumeAddrSpec()
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.s) debug.Printf("parseAddress: state is now %q", p.s)
...@@ -314,8 +320,13 @@ func (p *addrParser) parseAddress() (addr *Address, err error) { ...@@ -314,8 +320,13 @@ func (p *addrParser) parseAddress() (addr *Address, err error) {
} }
debug.Printf("parseAddress: displayName=%q", displayName) debug.Printf("parseAddress: displayName=%q", displayName)
// angle-addr = "<" addr-spec ">"
p.skipSpace() p.skipSpace()
if handleGroup {
if p.consume(':') {
return p.consumeGroupList()
}
}
// angle-addr = "<" addr-spec ">"
if !p.consume('<') { if !p.consume('<') {
return nil, errors.New("mail: no angle-addr") return nil, errors.New("mail: no angle-addr")
} }
...@@ -328,10 +339,42 @@ func (p *addrParser) parseAddress() (addr *Address, err error) { ...@@ -328,10 +339,42 @@ func (p *addrParser) parseAddress() (addr *Address, err error) {
} }
debug.Printf("parseAddress: spec=%q", spec) debug.Printf("parseAddress: spec=%q", spec)
return &Address{ return []*Address{{
Name: displayName, Name: displayName,
Address: spec, Address: spec,
}, nil }}, nil
}
func (p *addrParser) consumeGroupList() ([]*Address, error) {
var group []*Address
// handle empty group.
p.skipSpace()
if p.consume(';') {
p.skipCfws()
return group, nil
}
for {
p.skipSpace()
// embedded groups not allowed.
addrs, err := p.parseAddress(false)
if err != nil {
return nil, err
}
group = append(group, addrs...)
if !p.skipCfws() {
return nil, errors.New("mail: misformatted parenthetical comment")
}
if p.consume(';') {
p.skipCfws()
break
}
if !p.consume(',') {
return nil, errors.New("mail: expected comma")
}
}
return group, 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.
...@@ -489,7 +532,7 @@ Loop: ...@@ -489,7 +532,7 @@ Loop:
// If dot is true, consumeAtom parses an RFC 5322 dot-atom instead. // If dot is true, consumeAtom parses an RFC 5322 dot-atom instead.
// If permissive is true, consumeAtom will not fail on: // If permissive is true, consumeAtom will not fail on:
// - leading/trailing/double dots in the atom (see golang.org/issue/4938) // - 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) // - 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) { func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) {
i := 0 i := 0
...@@ -627,17 +670,17 @@ func (e charsetError) Error() string { ...@@ -627,17 +670,17 @@ func (e charsetError) Error() string {
// isAtext reports whether r is an RFC 5322 atext character. // isAtext reports whether r is an RFC 5322 atext character.
// If dot is true, period is included. // If dot is true, period is included.
// If permissive is true, RFC 5322 3.2.3 specials is included, // If permissive is true, RFC 5322 3.2.3 specials is included,
// except '<', '>' and '"'. // except '<', '>', ':' and '"'.
func isAtext(r rune, dot, permissive bool) bool { func isAtext(r rune, dot, permissive bool) bool {
switch r { switch r {
case '.': case '.':
return dot return dot
// RFC 5322 3.2.3. specials // RFC 5322 3.2.3. specials
case '(', ')', '[', ']', ':', ';', '@', '\\', ',': case '(', ')', '[', ']', ';', '@', '\\', ',':
return permissive return permissive
case '<', '>', '"': case '<', '>', '"', ':':
return false return false
} }
return isVchar(r) return isVchar(r)
......
...@@ -140,6 +140,10 @@ func TestAddressParsingError(t *testing.T) { ...@@ -140,6 +140,10 @@ func TestAddressParsingError(t *testing.T) {
8: {`<jdoe#machine.example>`, "missing @ in addr-spec"}, 8: {`<jdoe#machine.example>`, "missing @ in addr-spec"},
9: {`John <middle> Doe <jdoe@machine.example>`, "missing @ in addr-spec"}, 9: {`John <middle> Doe <jdoe@machine.example>`, "missing @ in addr-spec"},
10: {"cfws@example.com (", "misformatted parenthetical comment"}, 10: {"cfws@example.com (", "misformatted parenthetical comment"},
11: {"empty group: ;", "empty group"},
12: {"root group: embed group: null@example.com;", "no angle-addr"},
13: {"group not closed: null@example.com", "expected comma"},
14: {"group: first@example.com, second@example.com;", "group with multiple addresses"},
} }
for i, tc := range mustErrTestCases { for i, tc := range mustErrTestCases {
...@@ -243,8 +247,53 @@ func TestAddressParsing(t *testing.T) { ...@@ -243,8 +247,53 @@ func TestAddressParsing(t *testing.T) {
}}, }},
}, },
// RFC 5322, Appendix A.1.3 // RFC 5322, Appendix A.1.3
// TODO(dsymonds): Group addresses. {
`group1: groupaddr1@example.com;`,
[]*Address{
{
Name: "",
Address: "groupaddr1@example.com",
},
},
},
{
`empty group: ;`,
[]*Address(nil),
},
{
`A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
[]*Address{
{
Name: "Ed Jones",
Address: "c@a.test",
},
{
Name: "",
Address: "joe@where.test",
},
{
Name: "John",
Address: "jdoe@one.test",
},
},
},
{
`Group1: <addr1@example.com>;, Group 2: addr2@example.com;, John <addr3@example.com>`,
[]*Address{
{
Name: "",
Address: "addr1@example.com",
},
{
Name: "",
Address: "addr2@example.com",
},
{
Name: "John",
Address: "addr3@example.com",
},
},
},
// RFC 2047 "Q"-encoded ISO-8859-1 address. // RFC 2047 "Q"-encoded ISO-8859-1 address.
{ {
`=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`, `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
......
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