From: Mihail Minaev Date: Tue, 26 Sep 2017 11:47:49 +0000 (+0000) Subject: net/mail: parse group in email address X-Git-Tag: go1.10beta1~963 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=6f2e5f9c3720540ae5804b37222f6e82313ce13f;p=gostls13.git 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 ;` would be parsed into `Test `. Fixes #22014 Change-Id: I6e804a62f3ede04f555a1b82500b8ca030eeb431 Reviewed-on: https://go-review.googlesource.com/66250 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- diff --git a/src/net/mail/message.go b/src/net/mail/message.go index 9c29e812c0..903dbd0c40 100644 --- a/src/net/mail/message.go +++ b/src/net/mail/message.go @@ -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) diff --git a/src/net/mail/message_test.go b/src/net/mail/message_test.go index 17655f98b4..5d5d6e4e9c 100644 --- a/src/net/mail/message_test.go +++ b/src/net/mail/message_test.go @@ -140,6 +140,10 @@ func TestAddressParsingError(t *testing.T) { 8: {``, "missing @ in addr-spec"}, 9: {`John Doe `, "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 ,joe@where.test,John ;`, + []*Address{ + { + Name: "Ed Jones", + Address: "c@a.test", + }, + { + Name: "", + Address: "joe@where.test", + }, + { + Name: "John", + Address: "jdoe@one.test", + }, + }, + }, + { + `Group1: ;, Group 2: addr2@example.com;, John `, + []*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?= `,