From 339edc48f3d5060d7481e4fb002124b322ac8444 Mon Sep 17 00:00:00 2001 From: Sahana Bogar Date: Tue, 21 Jul 2026 22:23:32 +0530 Subject: [PATCH 1/3] require the IPv6: tag for bracketed IPv6 email literals --- .../validator/routines/EmailValidator.java | 13 ++++++++++--- .../validator/routines/EmailValidatorTest.java | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/commons/validator/routines/EmailValidator.java b/src/main/java/org/apache/commons/validator/routines/EmailValidator.java index 0eb58d1a9..19154c125 100644 --- a/src/main/java/org/apache/commons/validator/routines/EmailValidator.java +++ b/src/main/java/org/apache/commons/validator/routines/EmailValidator.java @@ -43,6 +43,8 @@ public class EmailValidator implements Serializable { private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; + // RFC 5321 section 4.1.3: an IPv6 address literal is prefixed with this tag, an IPv4 literal is not. + private static final String IPV6_TAG = "IPv6:"; private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$"; private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); @@ -195,9 +197,14 @@ protected boolean isValidDomain(final String domain) { final Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); if (ipDomainMatcher.matches()) { - final InetAddressValidator inetAddressValidator = - InetAddressValidator.getInstance(); - return inetAddressValidator.isValid(ipDomainMatcher.group(1)); + final InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance(); + final String ipLiteral = ipDomainMatcher.group(1); + // An IPv6 address literal carries the "IPv6:" tag and an IPv4 literal is untagged (RFC 5321). The tag + // is ABNF-literal text, so match it case insensitively rather than accepting a bare IPv6 address. + if (ipLiteral.regionMatches(true, 0, IPV6_TAG, 0, IPV6_TAG.length())) { + return inetAddressValidator.isValidInet6Address(ipLiteral.substring(IPV6_TAG.length())); + } + return inetAddressValidator.isValidInet4Address(ipLiteral); } // Domain is symbolic name if (allowTld) { diff --git a/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java index 87b3839a5..9ab3ddfd6 100644 --- a/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/EmailValidatorTest.java @@ -453,6 +453,23 @@ void testEmailWithNumericAddress() { assertTrue(validator.isValid("someone@yahoo.com")); } + /** + * Tests IPv6 address literals in the domain, which RFC 5321 section 4.1.3 tags with "IPv6:". + */ + @Test + void testEmailWithIpv6AddressLiteral() { + // Tagged IPv6 literals are accepted; the tag is ABNF-literal text so it is case insensitive. + assertTrue(validator.isValid("someone@[IPv6:2001:db8::1]")); + assertTrue(validator.isValid("someone@[IPv6:::1]")); + assertTrue(validator.isValid("someone@[ipv6:fe80::1]")); + // A bare IPv6 literal without the tag is not a valid address literal. + assertFalse(validator.isValid("someone@[2001:db8::1]")); + assertFalse(validator.isValid("someone@[::1]")); + // The tag is IPv6 only; an IPv4 literal stays untagged. + assertTrue(validator.isValid("someone@[216.109.118.76]")); + assertFalse(validator.isValid("someone@[IPv6:216.109.118.76]")); + } + /** * VALIDATOR-296 - A / or a ! is valid in the user part, but not in the domain part */ From 29558d4a56e5f34aa23e1a6232c67c0543c2015d Mon Sep 17 00:00:00 2001 From: Sahana Bogar Date: Wed, 22 Jul 2026 15:14:14 +0530 Subject: [PATCH 2/3] match the IPv6: tag in IP_DOMAIN_REGEX instead of string matching --- .../commons/validator/routines/EmailValidator.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/commons/validator/routines/EmailValidator.java b/src/main/java/org/apache/commons/validator/routines/EmailValidator.java index 19154c125..c78c17edc 100644 --- a/src/main/java/org/apache/commons/validator/routines/EmailValidator.java +++ b/src/main/java/org/apache/commons/validator/routines/EmailValidator.java @@ -42,9 +42,8 @@ public class EmailValidator implements Serializable { private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; - private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; - // RFC 5321 section 4.1.3: an IPv6 address literal is prefixed with this tag, an IPv4 literal is not. - private static final String IPV6_TAG = "IPv6:"; + // RFC 5321 section 4.1.3: an IPv6 address literal carries the "IPv6:" tag (case-insensitive), an IPv4 literal is untagged. + private static final String IP_DOMAIN_REGEX = "^\\[((?i)IPv6:)?(.*)\\]$"; private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$"; private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); @@ -198,13 +197,10 @@ protected boolean isValidDomain(final String domain) { if (ipDomainMatcher.matches()) { final InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance(); - final String ipLiteral = ipDomainMatcher.group(1); - // An IPv6 address literal carries the "IPv6:" tag and an IPv4 literal is untagged (RFC 5321). The tag - // is ABNF-literal text, so match it case insensitively rather than accepting a bare IPv6 address. - if (ipLiteral.regionMatches(true, 0, IPV6_TAG, 0, IPV6_TAG.length())) { - return inetAddressValidator.isValidInet6Address(ipLiteral.substring(IPV6_TAG.length())); + if (ipDomainMatcher.group(1) != null) { + return inetAddressValidator.isValidInet6Address(ipDomainMatcher.group(2)); } - return inetAddressValidator.isValidInet4Address(ipLiteral); + return inetAddressValidator.isValidInet4Address(ipDomainMatcher.group(2)); } // Domain is symbolic name if (allowTld) { From d0e24e9c71f92476d2a9a592bebae819718471a3 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Wed, 22 Jul 2026 08:15:01 -0400 Subject: [PATCH 3/3] Convert inline comment to Javadoc Added Javadoc comment for IP_DOMAIN_REGEX to clarify its purpose. --- .../apache/commons/validator/routines/EmailValidator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/validator/routines/EmailValidator.java b/src/main/java/org/apache/commons/validator/routines/EmailValidator.java index c78c17edc..460397456 100644 --- a/src/main/java/org/apache/commons/validator/routines/EmailValidator.java +++ b/src/main/java/org/apache/commons/validator/routines/EmailValidator.java @@ -42,7 +42,10 @@ public class EmailValidator implements Serializable { private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; - // RFC 5321 section 4.1.3: an IPv6 address literal carries the "IPv6:" tag (case-insensitive), an IPv4 literal is untagged. + + /** + * RFC 5321 section 4.1.3: an IPv6 address literal carries the "IPv6:" tag (case-insensitive), an IPv4 literal is untagged. + */ private static final String IP_DOMAIN_REGEX = "^\\[((?i)IPv6:)?(.*)\\]$"; private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$";