net/mail: parse ipv6 in addresses according to RFC 5321.

The new domain-literal support missed that IPv6 addresses need to be
prefixed with "IPv6:".

The IPv6-address-literal Specification, defined in RFC 5321 Section 4.1.3,
outlines the format for IPv6 address literals:
https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.3

Good news, the IANA has registered no other Address Literal Tags.

Updates #60206

Change-Id: I7796b0f78e386a6509e793a9db98404934797722
GitHub-Last-Rev: b65fdec3e96da76f16b42eb761d6aafd3b476377
GitHub-Pull-Request: golang/go#69461
Reviewed-on: https://go-review.googlesource.com/c/go/+/613335
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/src/net/mail/message.go b/src/net/mail/message.go
index 7c18c28..81acf01 100644
--- a/src/net/mail/message.go
+++ b/src/net/mail/message.go
@@ -764,7 +764,12 @@
 	}
 
 	// Check if the domain literal is an IP address
-	if net.ParseIP(dtext) == nil {
+	if addr, ok := strings.CutPrefix(dtext, "IPv6:"); ok {
+		if len(net.ParseIP(addr)) != net.IPv6len {
+			return "", fmt.Errorf("mail: invalid IPv6 address in domain-literal: %q", dtext)
+		}
+
+	} else if net.ParseIP(dtext).To4() == nil {
 		return "", fmt.Errorf("mail: invalid IP address in domain-literal: %q", dtext)
 	}
 
diff --git a/src/net/mail/message_test.go b/src/net/mail/message_test.go
index 80a4b2d..2c1198a 100644
--- a/src/net/mail/message_test.go
+++ b/src/net/mail/message_test.go
@@ -396,6 +396,7 @@
 		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"},
+		25: {"<jdoe@[fd42::de:ad:be:ef]>", "invalid IP address in domain-literal"},
 	}
 
 	for i, tc := range mustErrTestCases {
@@ -826,6 +827,20 @@
 				Address: "jdoe@[192.168.0.1]",
 			}},
 		},
+		// IPv6 Domain-literal
+		{
+			`jdoe@[IPv6:fd42::dead:beef:1234]`,
+			[]*Address{{
+				Address: "jdoe@[IPv6:fd42::dead:beef:1234]",
+			}},
+		},
+		{
+			`John Doe <jdoe@[IPv6:fd42::dead:beef:1234]>`,
+			[]*Address{{
+				Name:    "John Doe",
+				Address: "jdoe@[IPv6:fd42::dead:beef:1234]",
+			}},
+		},
 	}
 	for _, test := range tests {
 		if len(test.exp) == 1 {
@@ -990,6 +1005,20 @@
 				Address: "jdoe@[192.168.0.1]",
 			}},
 		},
+		// IPv6 Domain-literal
+		{
+			`jdoe@[IPv6:fd42::dead:beef:1234]`,
+			[]*Address{{
+				Address: "jdoe@[IPv6:fd42::dead:beef:1234]",
+			}},
+		},
+		{
+			`John Doe <jdoe@[IPv6:fd42::dead:beef:1234]>`,
+			[]*Address{{
+				Name:    "John Doe",
+				Address: "jdoe@[IPv6:fd42::dead:beef:1234]",
+			}},
+		},
 	}
 
 	ap := AddressParser{WordDecoder: &mime.WordDecoder{
@@ -1105,6 +1134,15 @@
 			&Address{Name: "Bob", Address: "bob@[192.168.0.1]"},
 			`"Bob" <bob@[192.168.0.1]>`,
 		},
+		// IPv6 Domain-literal
+		{
+			&Address{Address: "bob@[IPv6:fd42::dead:beef:1234]"},
+			"<bob@[IPv6:fd42::dead:beef:1234]>",
+		},
+		{
+			&Address{Name: "Bob", Address: "bob@[IPv6:fd42::dead:beef:1234]"},
+			`"Bob" <bob@[IPv6:fd42::dead:beef:1234]>`,
+		},
 	}
 	for _, test := range tests {
 		s := test.addr.String()