"io"
"log"
"mime"
+ "net"
"net/textproto"
"strings"
"sync"
if p.empty() {
return "", errors.New("mail: no domain in addr-spec")
}
- // TODO(dsymonds): Handle domain-literal
- domain, err = p.consumeAtom(true, false)
- if err != nil {
- return "", err
+
+ if p.peek() == '[' {
+ // domain-literal
+ domain, err = p.consumeDomainLiteral()
+ if err != nil {
+ return "", err
+ }
+ } else {
+ // dot-atom
+ domain, err = p.consumeAtom(true, false)
+ if err != nil {
+ return "", err
+ }
}
return localPart + "@" + domain, nil
return atom, nil
}
+// consumeDomainLiteral parses an RFC 5322 domain-literal at the start of p.
+func (p *addrParser) consumeDomainLiteral() (string, error) {
+ // Skip the leading [
+ if !p.consume('[') {
+ return "", errors.New(`mail: missing "[" in domain-literal`)
+ }
+
+ // Parse the dtext
+ var dtext string
+ for {
+ if p.empty() {
+ return "", errors.New("mail: unclosed domain-literal")
+ }
+ if p.peek() == ']' {
+ break
+ }
+
+ r, size := utf8.DecodeRuneInString(p.s)
+ if size == 1 && r == utf8.RuneError {
+ return "", fmt.Errorf("mail: invalid utf-8 in domain-literal: %q", p.s)
+ }
+ if !isDtext(r) {
+ return "", fmt.Errorf("mail: bad character in domain-literal: %q", r)
+ }
+
+ dtext += p.s[:size]
+ p.s = p.s[size:]
+ }
+
+ // Skip the trailing ]
+ if !p.consume(']') {
+ return "", errors.New("mail: unclosed domain-literal")
+ }
+
+ // Check if the domain literal is an IP address
+ if net.ParseIP(dtext) == nil {
+ return "", fmt.Errorf("mail: invalid IP address in domain-literal: %q", dtext)
+ }
+
+ return "[" + dtext + "]", nil
+}
+
func (p *addrParser) consumeDisplayNameComment() (string, error) {
if !p.consume('(') {
return "", errors.New("mail: comment does not start with (")
func isWSP(r rune) bool {
return r == ' ' || r == '\t'
}
+
+// isDtext reports whether r is an RFC 5322 dtext character.
+func isDtext(r rune) bool {
+ // Printable US-ASCII, excluding "[", "]", or "\".
+ if r == '[' || r == ']' || r == '\\' {
+ return false
+ }
+ return isVchar(r)
+}
18: {" group: null@example.com; (asd", "misformatted parenthetical comment"},
19: {" group: ; (asd", "misformatted parenthetical comment"},
20: {`(John) Doe <jdoe@machine.example>`, "missing word in phrase:"},
+ 21: {"<jdoe@[" + string([]byte{0xed, 0xa0, 0x80}) + "192.168.0.1]>", "invalid utf-8 in domain-literal"},
+ 22: {"<jdoe@[[192.168.0.1]>", "bad character in domain-literal"},
+ 23: {"<jdoe@[192.168.0.1>", "unclosed domain-literal"},
+ 24: {"<jdoe@[256.0.0.1]>", "invalid IP address in domain-literal"},
}
for i, tc := range mustErrTestCases {
},
},
},
+ // Domain-literal
+ {
+ `jdoe@[192.168.0.1]`,
+ []*Address{{
+ Address: "jdoe@[192.168.0.1]",
+ }},
+ },
+ {
+ `John Doe <jdoe@[192.168.0.1]>`,
+ []*Address{{
+ Name: "John Doe",
+ Address: "jdoe@[192.168.0.1]",
+ }},
+ },
}
for _, test := range tests {
if len(test.exp) == 1 {
},
},
},
+ // Domain-literal
+ {
+ `jdoe@[192.168.0.1]`,
+ []*Address{{
+ Address: "jdoe@[192.168.0.1]",
+ }},
+ },
+ {
+ `John Doe <jdoe@[192.168.0.1]>`,
+ []*Address{{
+ Name: "John Doe",
+ Address: "jdoe@[192.168.0.1]",
+ }},
+ },
}
ap := AddressParser{WordDecoder: &mime.WordDecoder{
&Address{Name: string([]byte{0xed, 0xa0, 0x80}), Address: "invalid-utf8@example.net"},
"=?utf-8?q?=ED=A0=80?= <invalid-utf8@example.net>",
},
+ // Domain-literal
+ {
+ &Address{Address: "bob@[192.168.0.1]"},
+ "<bob@[192.168.0.1]>",
+ },
+ {
+ &Address{Name: "Bob", Address: "bob@[192.168.0.1]"},
+ `"Bob" <bob@[192.168.0.1]>`,
+ },
}
for _, test := range tests {
s := test.addr.String()
`<"."@example.com>`,
`<".."@example.com>`,
`<"0:"@0>`,
+ `<Bob@[192.168.0.1]>`,
}
for _, test := range tests {