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.
Notable divergences:
* Obsolete address formats are not parsed, including addresses with
embedded route information.
* Group addresses are not parsed.
* The full range of spacing (the CFWS syntax element) is not supported,
such as breaking addresses across lines.
* No unicode normalization is performed.
......@@ -248,11 +247,11 @@ func (p *addrParser) parseAddressList() ([]*Address, error) {
var list []*Address
for {
p.skipSpace()
addr, err := p.parseAddress()
addrs, err := p.parseAddress(true)
if err != nil {
return nil, err
}
list = append(list, addr)
list = append(list, addrs...)
if !p.skipCfws() {
return nil, errors.New("mail: misformatted parenthetical comment")
......@@ -268,7 +267,7 @@ func (p *addrParser) parseAddressList() ([]*Address, error) {
}
func (p *addrParser) parseSingleAddress() (*Address, error) {
addr, err := p.parseAddress()
addrs, err := p.parseAddress(true)
if err != nil {
return nil, err
}
......@@ -278,28 +277,35 @@ func (p *addrParser) parseSingleAddress() (*Address, error) {
if !p.empty() {
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.
func (p *addrParser) parseAddress() (addr *Address, err error) {
func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
debug.Printf("parseAddress: %q", p.s)
p.skipSpace()
if p.empty() {
return nil, errors.New("mail: no address")
}
// address = name-addr / addr-spec
// TODO(dsymonds): Support parsing group address.
// address = mailbox / group
// mailbox = name-addr / addr-spec
// group = display-name ":" [group-list] ";" [CFWS]
// addr-spec has a more restricted grammar than name-addr,
// so try parsing it first, and fallback to name-addr.
// TODO(dsymonds): Is this really correct?
spec, err := p.consumeAddrSpec()
if err == nil {
return &Address{
return []*Address{{
Address: spec,
}, err
}}, err
}
debug.Printf("parseAddress: not an addr-spec: %v", err)
debug.Printf("parseAddress: state is now %q", p.s)
......@@ -314,8 +320,13 @@ func (p *addrParser) parseAddress() (addr *Address, err error) {
}
debug.Printf("parseAddress: displayName=%q", displayName)
// angle-addr = "<" addr-spec ">"
p.skipSpace()
if handleGroup {
if p.consume(':') {
return p.consumeGroupList()
}
}
// angle-addr = "<" addr-spec ">"
if !p.consume('<') {
return nil, errors.New("mail: no angle-addr")
}
......@@ -328,10 +339,42 @@ func (p *addrParser) parseAddress() (addr *Address, err error) {
}
debug.Printf("parseAddress: spec=%q", spec)
return &Address{
return []*Address{{
Name: displayName,
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.
......@@ -489,7 +532,7 @@ 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)
// - 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
......@@ -627,17 +670,17 @@ 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 '"'.
// except '<', '>', ':' and '"'.
func isAtext(r rune, dot, permissive bool) bool {
switch r {
case '.':
return dot
// RFC 5322 3.2.3. specials
case '(', ')', '[', ']', ':', ';', '@', '\\', ',':
case '(', ')', '[', ']', ';', '@', '\\', ',':
return permissive
case '<', '>', '"':
case '<', '>', '"', ':':
return false
}
return isVchar(r)
......
......@@ -140,6 +140,10 @@ func TestAddressParsingError(t *testing.T) {
8: {`<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"},
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 {
......@@ -243,8 +247,53 @@ func TestAddressParsing(t *testing.T) {
}},
},
// 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.
{
`=?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