]> Cypherpunks repositories - gostls13.git/commitdiff
net/mail: parse group in email address
authorMihail Minaev <minaev.mike@gmail.com>
Tue, 26 Sep 2017 11:47:49 +0000 (11:47 +0000)
committerIan Lance Taylor <iant@golang.org>
Wed, 27 Sep 2017 13:55:10 +0000 (13:55 +0000)
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: Ian Lance Taylor <iant@golang.org>
src/net/mail/message.go
src/net/mail/message_test.go

index 9c29e812c01afebaa14c4fbdccbe2a3acc83a22c..903dbd0c4015c22b96d68337bb219f8ada310e63 100644 (file)
@@ -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)
index 17655f98b44f25ff991066a79ce33ebac236d614..5d5d6e4e9ca835d9010c773f05cb3aa2e0f78255 100644 (file)
@@ -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>`,