Skip to content

Commit

Permalink
Add option to validate MX record (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
RohanNagar committed Mar 20, 2023
1 parent a0daa5b commit 4b765dc
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 7 deletions.
12 changes: 12 additions & 0 deletions README.md
Expand Up @@ -266,6 +266,18 @@ You can require that your `EmailValidator` reject all emails that have obsolete
JMail.validator().disallowObsoleteWhitespace();
```

#### Require a valid MX record

You can require that your `EmailValidator` reject all email addresses that do not have a valid MX
record associated with the domain.

> **Please note that since this rule looks up DNS records, including this rule on your email validator can significantly increase the
amount of time it takes to validate email addresses.**

```java
JMail.validator().requireValidMXRecord();
```

### Bonus: IP Address Validation

Since validating email addresses requires validation of IP addresses,
Expand Down
2 changes: 1 addition & 1 deletion checkstyle.xml
Expand Up @@ -194,7 +194,7 @@
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="ImportOrder">
<property name="groups" value="*,com,jakarta,java,net,nl,org"/>
<property name="groups" value="*,com,jakarta,java,javax,net,nl,org"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
<property name="option" value="bottom"/>
Expand Down
6 changes: 0 additions & 6 deletions pom.xml
Expand Up @@ -323,11 +323,5 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
16 changes: 16 additions & 0 deletions src/main/java/com/sanctionco/jmail/EmailValidator.java
Expand Up @@ -42,6 +42,8 @@ public final class EmailValidator {
= ValidationRules::disallowReservedDomains;
private static final Predicate<Email> DISALLOW_OBSOLETE_WHITESPACE_PREDICATE
= ValidationRules::disallowObsoleteWhitespace;
private static final Predicate<Email> REQUIRE_VALID_MX_RECORD_PREDICATE
= ValidationRules::requireValidMXRecord;

private final Set<Predicate<Email>> validationPredicates;

Expand Down Expand Up @@ -189,6 +191,20 @@ public EmailValidator disallowObsoleteWhitespace() {
return withRule(DISALLOW_OBSOLETE_WHITESPACE_PREDICATE);
}

/**
* Create a new {@code EmailValidator} with all rules from the current instance and the
* {@link ValidationRules#requireValidMXRecord(Email)} rule.
* Email addresses that have a domain without a valid MX record will fail validation.
*
* <p><strong>NOTE: Adding this rule to your EmailValidator may significantly increase
* the amount of time it takes to validate email addresses.</strong>
*
* @return the new {@code EmailValidator} instance
*/
public EmailValidator requireValidMXRecord() {
return withRule(REQUIRE_VALID_MX_RECORD_PREDICATE);
}

/**
* Return true if the given email address is valid according to all registered validation rules,
* or false otherwise. See {@link JMail#tryParse(String)} for details on the basic
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/sanctionco/jmail/ValidationRules.java
@@ -1,5 +1,7 @@
package com.sanctionco.jmail;

import com.sanctionco.jmail.dns.DNSLookupUtil;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -136,4 +138,14 @@ public static boolean disallowReservedDomains(Email email) {

return true;
}

/**
* Rejects an email address that does not have a valid MX record for the domain.
*
* @param email the email address to validate
* @return true if this email address has a valid MX record, or false if it does not
*/
public static boolean requireValidMXRecord(Email email) {
return DNSLookupUtil.hasMXRecord(email.domainWithoutComments());
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/sanctionco/jmail/dns/DNSLookupUtil.java
@@ -0,0 +1,40 @@
package com.sanctionco.jmail.dns;

import java.util.Hashtable;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/**
* Utility class that provides static methods for DNS related operations.
*/
public final class DNSLookupUtil {

/**
* Private constructor to prevent instantiation.
*/
private DNSLookupUtil() {
}

/**
* Determine if the given domain has a valid MX record.
*
* @param domain the domain whose MX record to check
* @return true if the domain has a valid MX record, or false if it does not
*/
public static boolean hasMXRecord(String domain) {
Hashtable<String, String> env = new Hashtable<>();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");

try {
DirContext ctx = new InitialDirContext(env);
Attribute attr = ctx.getAttributes(domain, new String[]{"MX"}).get("MX");

return attr != null && attr.size() > 0;
} catch (NamingException e) {
return false;
}
}
}
3 changes: 3 additions & 0 deletions src/main/java/module-info.java
Expand Up @@ -4,6 +4,9 @@
* @author Rohan Nagar (rohannagar11@gmail.com)
*/
open module com.sanctionco.jmail {
requires java.naming;

exports com.sanctionco.jmail;
exports com.sanctionco.jmail.dns;
exports com.sanctionco.jmail.net;
}
18 changes: 18 additions & 0 deletions src/test/java/com/sanctionco/jmail/EmailValidatorTest.java
Expand Up @@ -182,6 +182,24 @@ void allowsOtherAddresses(String email) {
}
}

@Nested
class RequireValidMXRecord {
@ParameterizedTest(name = "{0}")
@ValueSource(strings = {
"test@domain.test", "test@domain.example", "test@domain.invalid", "test@domain.localhost"})
void rejectsDomainsWithoutMXRecord(String email) {
runInvalidTest(JMail.validator().requireValidMXRecord(), email);
}

@ParameterizedTest(name = "{0}")
@ValueSource(strings = {
"test@gmail.com", "test@hotmail.com", "test@yahoo.com", "test@utexas.edu",
"test@gmail.(comment)com"})
void allowsDomansWithMXRecord(String email) {
runValidTest(JMail.validator().requireValidMXRecord(), email);
}
}

@Nested
class CustomRule {
@ParameterizedTest(name = "{0}")
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/com/sanctionco/jmail/dns/DNSLookupUtilTest.java
@@ -0,0 +1,22 @@
package com.sanctionco.jmail.dns;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class DNSLookupUtilTest {

@Test
void canLookupValidMXRecord() {
assertThat(DNSLookupUtil.hasMXRecord("gmail.com")).isTrue();
assertThat(DNSLookupUtil.hasMXRecord("yahoo.com")).isTrue();
assertThat(DNSLookupUtil.hasMXRecord("hotmail.com")).isTrue();
assertThat(DNSLookupUtil.hasMXRecord("utexas.edu")).isTrue();
}

@Test
void failsToFindInvalidMXRecord() {
assertThat(DNSLookupUtil.hasMXRecord("a.com")).isFalse();
assertThat(DNSLookupUtil.hasMXRecord("whatis.hello")).isFalse();
}
}

0 comments on commit 4b765dc

Please sign in to comment.