Last active
July 7, 2020 21:03
-
-
Save rekire/7072650 to your computer and use it in GitHub Desktop.
Implementation for validating email addresses, it supports IDN domains and it checks if the domain has a mx record. It requires the xbill lib (http://www.xbill.org/dnsjava/).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @copyright | |
* This code is licensed under the Rekisoft Public License. | |
* See http://www.rekisoft.eu/licenses/rkspl.html for more informations. | |
*/ | |
package eu.rekisoft.android.util; | |
import java.net.IDN; | |
import java.util.Arrays; | |
import org.xbill.DNS.Lookup; | |
import org.xbill.DNS.MXRecord; | |
import org.xbill.DNS.Record; | |
import org.xbill.DNS.TextParseException; | |
import org.xbill.DNS.Type; | |
import android.annotation.TargetApi; | |
import android.os.Build; | |
import android.os.Build.VERSION_CODES; | |
import android.util.Log; | |
/** | |
* Tool set for verifying email addressed on Android based on the DNS utils from xbill. | |
* | |
* @author rekire | |
* @version 0.8 | |
* @copyright This code is licensed under the Rekisoft Public License.<br/> | |
* See http://www.rekisoft.eu/licenses/rkspl.html for more informations. | |
*/ | |
public final class MailChecker { | |
/** | |
* The status of the mail address verification. | |
* | |
* @author rekire | |
*/ | |
public static enum AddressStatus { | |
/** The address seems to be fine */ | |
valid, | |
/** The address has some schema errors like a missing at sign */ | |
wrongSchema, | |
/** The domain is currently not registed */ | |
notRegisted, | |
/** The domain has no MX record (so the domain cannot receive mails) */ | |
noMxRecord, | |
/** A typographic error was detected */ | |
typoDetected, | |
/** The status of the address is unknown */ | |
unknown, | |
/** The mail address check is pending */ | |
pending; | |
private String mail; | |
/** | |
* @return the suggested mail address. | |
*/ | |
public String getMailAddress() { | |
return mail; | |
} | |
/** | |
* Set the email address in case of a detected typo. | |
* | |
* @param mail | |
* The mail address which is guessed. | |
* @return this. | |
*/ | |
protected AddressStatus setMailAddress(String mail) { | |
this.mail = mail; | |
return this; | |
} | |
/** | |
* @return <code>true</code> if the email address is not valid or a typo was detected. | |
*/ | |
public boolean wrong() { | |
return this != valid && this != typoDetected; | |
} | |
} | |
/** | |
* A small list of well known email addresses. | |
*/ | |
private static String[] domains = {"web.de", "gmx.de", "gmx.com", "gmx.net", "freenet.net", "hotmail.com", "gmail.com", | |
"googlemail.com", "live.de", "live.com", "hotmail.de", "aol.com", "t-online.de", "hushmail.com", "uni.de", "yahoo.com", | |
"yahoo.de"}; | |
static { | |
// Set the network timeout to one second. | |
Lookup.getDefaultResolver().setTimeout(1); | |
} | |
/** | |
* Let you set your own list of domains which you want to use for auto correction. | |
* | |
* @param list | |
* Your list of well known domains. | |
*/ | |
public static void setDomainList(String[] list) { | |
if(list != null) { | |
domains = list; | |
} | |
} | |
/** | |
* Checks if a mail address is correct and tries to correct it if possible. | |
* | |
* @param mail | |
* the input mail address. | |
* @return the status of the validation with a copy of the mail address on success. | |
*/ | |
public static MailChecker.AddressStatus validate(String mail) { | |
String domain = getDomain(mail); | |
if(domain == null) { | |
// System.err.println(mail + " is no valid email address"); | |
return AddressStatus.wrongSchema; | |
} | |
try { | |
if(validateMxServer(domain)) { | |
// System.out.println(mail + " is ok"); | |
return AddressStatus.valid.setMailAddress(mail); | |
} else { | |
for(String d : domains) { | |
if(damerauLevenshteinDistance(d, domain, 128) <= 2) { | |
// System.out.println(mail + " did you mean " + d + "?"); | |
return AddressStatus.typoDetected.setMailAddress(mail.substring(0, mail.indexOf("@") + 1) + d); | |
} | |
} | |
if(doesDomainExists(domain)) { | |
// System.err.println(mail + " has no mail servers"); | |
return AddressStatus.noMxRecord; | |
} else { | |
// System.err.println("Domain \"" + domain + "\" does not exists"); | |
return AddressStatus.notRegisted; | |
} | |
} | |
} catch(IllegalStateException e) { | |
return AddressStatus.unknown; | |
} | |
} | |
/** | |
* Checks if a NS record exists for the given domain. | |
* | |
* @param domain | |
* the domain which should be checked. | |
* @return true if a NS record was found. | |
* @throws IllegalStateException | |
* on network errors. | |
*/ | |
private static boolean doesDomainExists(String domain) throws IllegalStateException { | |
try { | |
Lookup l = new Lookup(domain, Type.NS); | |
Record[] result = l.run(); | |
if(l.getResult() == Lookup.TRY_AGAIN) { | |
throw new IllegalStateException(); | |
} | |
return result != null && result.length > 0; | |
} catch(TextParseException e) { | |
} | |
return false; | |
} | |
/** | |
* Returns the domain portion of the mail address. | |
* | |
* @param mail | |
* the input mail address. | |
* @return <code>null</code> if the mail address is malformed or the domain. | |
*/ | |
@TargetApi(Build.VERSION_CODES.GINGERBREAD) | |
private static String getDomain(String mail) { | |
String[] part = mail.split("@"); | |
if(part.length != 2) { | |
Log.w(MailChecker.class.getSimpleName(), "Syntax error on " + mail); | |
return null; | |
} | |
if(Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) { | |
return IDN.toASCII(part[1]); | |
} else { | |
Log.w(MailChecker.class.getSimpleName(), "No IDN support on this platform"); | |
return part[1]; | |
} | |
} | |
/** | |
* Checks if a MX record exists for the given domain. | |
* | |
* @param domain | |
* the domain which should be checked. | |
* @return true if a MX record was found. | |
* @throws IllegalStateException | |
* on network errors. | |
*/ | |
private static boolean validateMxServer(String domain) throws IllegalStateException { | |
try { | |
Lookup l = new Lookup(domain, Type.MX); | |
Record[] result = l.run(); | |
if(l.getResult() == Lookup.TRY_AGAIN) { | |
throw new IllegalStateException(); | |
} | |
if(result != null && result.length > 0) { | |
for(Record r : result) { | |
MXRecord mx = (MXRecord)r; | |
Record[] result2 = new Lookup(mx.getTarget(), Type.A).run(); | |
if(result2 != null && result2.length > 0) { | |
// System.out.println("OK! MX: " + mx.getTarget() + " -> " + ((ARecord)result2[0]).getAddress().getHostAddress()); | |
return true; | |
} else { | |
// System.err.println("Fail: could not resolv " + mx.getTarget()); | |
} | |
} | |
} | |
} catch(TextParseException e) { | |
e.printStackTrace(); | |
} | |
// System.err.println("Fail: not mx record for " + domain + " found"); | |
return false; | |
} | |
/** | |
* Calculated the Damerau-Levenshtein-Distance. | |
* | |
* @param a | |
* input string one. | |
* @param b | |
* input string two. | |
* @param alphabetLength | |
* the length of the alphabet. | |
* @return the distance. | |
* | |
* @author M. Jessup (http://stackoverflow.com/a/6035519/995926) | |
*/ | |
private static int damerauLevenshteinDistance(String a, String b, int alphabetLength) { | |
final int INFINITY = a.length() + b.length(); | |
int[][] H = new int[a.length() + 2][b.length() + 2]; | |
H[0][0] = INFINITY; | |
for(int i = 0; i <= a.length(); i++) { | |
H[i + 1][1] = i; | |
H[i + 1][0] = INFINITY; | |
} | |
for(int j = 0; j <= b.length(); j++) { | |
H[1][j + 1] = j; | |
H[0][j + 1] = INFINITY; | |
} | |
int[] DA = new int[alphabetLength]; | |
Arrays.fill(DA, 0); | |
for(int i = 1; i <= a.length(); i++) { | |
int DB = 0; | |
for(int j = 1; j <= b.length(); j++) { | |
int i1 = DA[b.charAt(j - 1)]; | |
int j1 = DB; | |
int d = ((a.charAt(i - 1) == b.charAt(j - 1)) ? 0 : 1); | |
if(d == 0) | |
DB = j; | |
H[i + 1][j + 1] = min(H[i][j] + d, H[i + 1][j] + 1, H[i][j + 1] + 1, H[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1)); | |
} | |
DA[a.charAt(i - 1)] = i; | |
} | |
return H[a.length() + 1][b.length() + 1]; | |
} | |
/** | |
* Helper function for getting the minimal value of a set of ints. | |
* | |
* @param nums | |
* The integer values which should been checked. | |
* @return the minimal value. | |
*/ | |
private static int min(int... nums) { | |
int min = Integer.MAX_VALUE; | |
for(int num : nums) { | |
min = min < num ? min : num; | |
} | |
return min; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment