Skip to content

Commit 50b835f

Browse files
authored
Merge pull request #490 from maxmind/greg/eng-2154
Replace use of commons-validator
2 parents 801bc15 + dc526d7 commit 50b835f

File tree

5 files changed

+129
-16
lines changed

5 files changed

+129
-16
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
CHANGELOG
22
=========
33

4+
3.8.0
5+
------------------
6+
7+
* `commons-validator` has been removed as a dependency. This module now does
8+
its own email and domain name validation. This was done to reduce the number
9+
of dependencies and any security vulnerabilities in them. The new email
10+
validation of the local part is somewhat more lax than the previous
11+
validation.
12+
413
3.7.2 (2025-05-28)
514
------------------
615

pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,6 @@
6262
<artifactId>geoip2</artifactId>
6363
<version>4.3.1</version>
6464
</dependency>
65-
<dependency>
66-
<groupId>commons-validator</groupId>
67-
<artifactId>commons-validator</artifactId>
68-
<version>1.9.0</version>
69-
</dependency>
7065
<dependency>
7166
<groupId>org.junit.jupiter</groupId>
7267
<artifactId>junit-jupiter</artifactId>

src/main/java/com/maxmind/minfraud/request/Email.java

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import java.util.HashMap;
1414
import java.util.Map;
1515
import java.util.regex.Pattern;
16-
import org.apache.commons.validator.routines.DomainValidator;
17-
import org.apache.commons.validator.routines.EmailValidator;
1816

1917
/**
2018
* The email information for the transaction.
@@ -28,6 +26,9 @@ public final class Email extends AbstractModel {
2826
private static final Map<String, String> equivalentDomains;
2927
private static final Map<String, Boolean> fastmailDomains;
3028
private static final Map<String, Boolean> yahooDomains;
29+
private static final Pattern DOMAIN_LABEL_PATTERN = Pattern.compile(
30+
"^[a-zA-Z0-9]$|^[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9]$"
31+
);
3132
private static final Pattern DOT_PATTERN = Pattern.compile("\\.");
3233
private static final Pattern TRAILING_DOT_PATTERN = Pattern.compile("\\.+$");
3334
private static final Pattern REPEAT_COM_PATTERN = Pattern.compile("(?:\\.com){2,}$");
@@ -338,7 +339,7 @@ public Builder(boolean enableValidation) {
338339
* @throws IllegalArgumentException when address is not a valid email address.
339340
*/
340341
public Email.Builder address(String address) {
341-
if (enableValidation && !EmailValidator.getInstance().isValid(address)) {
342+
if (enableValidation && !isValidEmail(address)) {
342343
throw new IllegalArgumentException(
343344
"The email address " + address + " is not valid.");
344345
}
@@ -373,7 +374,7 @@ public Email.Builder hashAddress() {
373374
* @throws IllegalArgumentException when domain is not a valid domain.
374375
*/
375376
public Email.Builder domain(String domain) {
376-
if (enableValidation && !DomainValidator.getInstance().isValid(domain)) {
377+
if (enableValidation && !isValidDomain(domain)) {
377378
throw new IllegalArgumentException("The email domain " + domain + " is not valid.");
378379
}
379380
this.domain = domain;
@@ -458,6 +459,34 @@ private String cleanAddress(String address) {
458459
return localPart + "@" + domain;
459460
}
460461

462+
private static boolean isValidEmail(String email) {
463+
if (email == null || email.isEmpty()) {
464+
return false;
465+
}
466+
467+
// In RFC 5321, the forward path limits the mailbox to 254 characters
468+
// even though a domain can be 255 and the local part 64
469+
if (email.length() > 254) {
470+
return false;
471+
}
472+
473+
int atIndex = email.lastIndexOf('@');
474+
if (atIndex <= 0) {
475+
return false;
476+
}
477+
478+
String localPart = email.substring(0, atIndex);
479+
String domainPart = email.substring(atIndex + 1);
480+
481+
// The local-part has a maximum length of 64 characters.
482+
if (localPart.length() > 64) {
483+
return false;
484+
}
485+
486+
return isValidDomain(domainPart);
487+
}
488+
489+
461490
private String cleanDomain(String domain) {
462491
if (domain == null) {
463492
return null;
@@ -491,6 +520,48 @@ private String cleanDomain(String domain) {
491520
return domain;
492521
}
493522

523+
private static boolean isValidDomain(String domain) {
524+
if (domain == null || domain.isEmpty()) {
525+
return false;
526+
}
527+
528+
try {
529+
domain = IDN.toASCII(domain);
530+
} catch (IllegalArgumentException e) {
531+
return false;
532+
}
533+
534+
if (domain.endsWith(".")) {
535+
domain = domain.substring(0, domain.length() - 1);
536+
}
537+
538+
if (domain.length() > 255) {
539+
return false;
540+
}
541+
542+
String[] labels = domain.split("\\.");
543+
544+
if (labels.length < 2) {
545+
return false;
546+
}
547+
548+
for (String label : labels) {
549+
if (!isValidDomainLabel(label)) {
550+
return false;
551+
}
552+
}
553+
554+
return true;
555+
}
556+
557+
private static boolean isValidDomainLabel(String label) {
558+
if (label == null || label.isEmpty() || label.length() > 63) {
559+
return false;
560+
}
561+
562+
return DOMAIN_LABEL_PATTERN.matcher(label).matches();
563+
}
564+
494565
/**
495566
* @return The domain of the email address used in the transaction.
496567
*/

src/main/java/module-info.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
requires com.fasterxml.jackson.databind;
88
requires com.fasterxml.jackson.datatype.jsr310;
99
requires transitive com.maxmind.geoip2;
10-
requires org.apache.commons.validator;
1110
requires java.net.http;
1211

1312
exports com.maxmind.minfraud;

src/test/java/com/maxmind/minfraud/request/EmailTest.java

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.jupiter.api.Assertions.assertNull;
55
import static org.junit.jupiter.api.Assertions.assertThrows;
66

7+
78
import com.maxmind.minfraud.request.Email.Builder;
89
import java.math.BigInteger;
910
import java.nio.charset.StandardCharsets;
@@ -12,6 +13,8 @@
1213
import java.util.HashMap;
1314
import java.util.Map;
1415
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.ValueSource;
1518

1619
public class EmailTest {
1720

@@ -242,11 +245,26 @@ private String toMD5(String s) throws NoSuchAlgorithmException {
242245
return String.format("%032x", i);
243246
}
244247

245-
@Test
246-
public void testInvalidAddress() {
248+
@ParameterizedTest(name = "Run #{index}: email = \"{0}\"")
249+
@ValueSource(strings = {
250+
"test.com",
251+
"test@",
252+
"@test.com",
253+
"",
254+
" ",
255+
"test@test com.com",
256+
"test@test_domain.com",
257+
258+
259+
260+
261+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@test.com",
262+
"test@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com"
263+
})
264+
void testInvalidAddresses(String invalidAddress) {
247265
assertThrows(
248266
IllegalArgumentException.class,
249-
() -> new Builder().address("a@[email protected]").build()
267+
() -> new Builder().address(invalidAddress).build()
250268
);
251269
}
252270

@@ -264,11 +282,32 @@ public void testDomainWithoutValidation() {
264282
assertEquals(domain, email.getDomain());
265283
}
266284

267-
@Test
268-
public void testInvalidDomain() {
285+
@ParameterizedTest(name = "Run #{index}: domain = \"{0}\"")
286+
@ValueSource(strings = {
287+
"example",
288+
"",
289+
" ",
290+
" domain.com",
291+
"domain.com ",
292+
"domain com.com",
293+
"domain_name.com",
294+
"domain$.com",
295+
"-domain.com",
296+
"domain-.com",
297+
"domain..com",
298+
".domain.com",
299+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
300+
"a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a" +
301+
".a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a" +
302+
".a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a" +
303+
".a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a" +
304+
".com",
305+
"xn--.com"
306+
})
307+
void testInvalidDomains(String invalidDomain) {
269308
assertThrows(
270309
IllegalArgumentException.class,
271-
() -> new Builder().domain(" domain.com").build()
310+
() -> new Builder().domain(invalidDomain).build()
272311
);
273312
}
274313
}

0 commit comments

Comments
 (0)