Categorie
Utilities

Un approccio pure java per la validazione di indirizzi email

Esistono diversi servizi a pagamento che consentono di spedire newsletter, tuttavia chi, come noi, è dotato di un SMTP proprio su server dedicato e non deve spedire milioni di mail, ma solo qualche decina di migliaia, può pensare di crearsi un tool molto semplice per farselo gratis ed in casa. In quest’ottica, mantenere una lista di indirizzi pulita e libera da typo o mail non più esistenti (domini sbagliati, username sbagliati) diventa una priorità per risparmiare risorse server ed evitare le centinaia di mail  di ritorno (spesso in più mandate per i vari retrial). A questo scopo ho realizzato un validatore email che sfrutta tre principi, il banale syntax checking, la validazione del record MX associato al dominio, ed infine la validazione della mailbox tramite il protocollo SMTP (i vari HELO, MAIL FROM, RCPT TO).

In questo post descriverò i primi due metodi di validazione, fornendo spezzoni di codici ready-to-use per chi volesse approfittarne.

Email syntax check

La validazione della sintassi di un indirizzo email in Java può essere affrontata in diversi modi, per garantire una perfetta compliance con la RFC 2822. In prima battuta si potrebbe pensare di utilizzare la classe EmailValidator, inclusa nel package Apache Commons, org.apache.commons.validator.routines. Questa classe, pensata proprio per la validazione di indirizzi mail, purtroppo non è utilizzabile poichè include un riferimento a org.apache.oro.text.perl.Perl5Util che è una classe di un progetto obsoleto, Jakarta ORO project.

Un’altra alternativa potrebbe essere utilizzare il seguente spezzone di codice, che utilizza la classe InetAddress, del package javax.mail.

public static boolean isValidEmailAddress(String email)
{
  boolean result = true;
  try {
    InternetAddress emailAddr = new InternetAddress(email);
    emailAddr.validate();
  } catch (AddressException ex) {
    result = false;
  }
  return result;
}

Lo svantaggio in questo metodo è quello di dover includere la libreria javax.mail, oltre al fatto che la validazione di questa classe considera corretti indirizzi formati con IP address nella parte host, come ad esempio luca@192.168.16.23. Questo chiaramente non è corretto qualora si voglia utilizzare la validazione in un classico form di registrazione.

A questo punto la scelta ricade sull’utilizzo delle regular expressions. Esistono decine di thread in rete, personalmente ne ho adattato uno che funziona bene, anche se esistono dei corner case dove può fallire.

private boolean mailSyntaxCheck(String email)
   {
        // Create the Pattern using the regex
        Pattern p = Pattern.compile(".+@.+\\.[a-z]+");

        // Match the given string with the pattern
        Matcher m = p.matcher(email);

        // check whether match is found
        boolean matchFound = m.matches();

        StringTokenizer st = new StringTokenizer(email, ".");
        String lastToken = null;
        while (st.hasMoreTokens()) {
            lastToken = st.nextToken();
        }

	// validate the country code
        if (matchFound && lastToken.length() >= 2
                && email.length() - 1 != lastToken.length()) {

            return true;
        } else {
            return false;
        }

    }

Verifica record MX

Una volta verificato che sintatticamente la mail sia valida, è possibile utilizzare la parte del dominio, ovvero tutto ciò che segue l’@, per validare la possibilità di inviare mail verso quel preciso dominio. Una verifica di questo tipo si può facilmente eseguire verificando se esiste o meno un record DNS di tipo MX associato a quel dominio (banalmente da shell si potrebbe utilizzare nslookup).

Se il dominio non ha record MX sicuramente non può ricevere mail. Viceversa, se il dominio ha un record MX potrebbe ricevere mail, anche se è necessaria una verifica ulteriore per avere una ragionevole certezza sulla validità dell’indirizzo.

La classe sotto può essere utilizzata per validare la presenza di record MX, ed escludere dalla lista delle email valide tutte quelle che hanno un dominio privo di record MX.


import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/**
*
* @author Luca Adamo, ELbuild - 2011
*/

public class MXLookupTest {

/*
* Call this method to validate the address, it assumes there's already been
* a syntax validation on the address (i.e. it has a domain part..)
*/

public static boolean test(String address)
{

  try {

  // Get the domain part from the address and check if it has MX records
   return !getDomainMXRecords(getDomainFromAddress(address)).isEmpty();
  } catch (NamingException ex) {
    Logger.getLogger(MXLookupTest.class.getName()).log(Level.SEVERE, null, ex);
    return false;
  } catch (Exception ex) {
    Logger.getLogger(MXLookupTest.class.getName()).log(Level.SEVERE, null, ex);
    return false;
  }

}

private static String getDomainFromAddress(String address) {

  // Find the separator for the domain name
  int pos = address.indexOf('@');

  // If the address does not contain an '@', it's not valid
  if (pos == -1) {
    return null;
  }// Isolate the domain/machine name
  else {
    return address.substring(++pos);
  }
}

private static List<String> getDomainMXRecords(String hostName) throws NamingException {

  // Perform a DNS lookup for MX records in the domain
  Properties env = new Properties();
  env.put("java.naming.factory.initial","com.sun.jndi.dns.DnsContextFactory");

  DirContext ictx = new InitialDirContext(env);
  Attributes attrs = ictx.getAttributes(hostName, new String[]{"MX"});

  // first query our dns cache to see if we have a record for that domain
  // trying to save bandwidth

  Attribute attr = attrs.get("MX");

  // if we don't have an MX record, try the machine itself
  if ((attr == null) || (attr.size() == 0)) {
    attrs = ictx.getAttributes(hostName, new String[]{"A"});
    attr = attrs.get("A");
    if (attr == null) {
      throw new NamingException("No MX records for '" + hostName + "'");
    } else {
      System.out.println(attr);
    }

  }

  // If the above exception is not thrown it means we have a list of MX records
  // to try, so we return them as an array

  ArrayList res = new ArrayList();
  NamingEnumeration en = attr.getAll();

  while (en.hasMore()) {
    String mailhost;
    String x = (String) en.next();

    String f[] = x.split(" ");
    if (f.length == 1) {
      mailhost = f[0];
    } else if (f[1].endsWith(".")) {
      mailhost = f[1].substring(0, (f[1].length() - 1));
    } else {
      mailhost = f[1];
    }
    res.add(mailhost);
  }

  return res;
  }

}

Questa classe può essere utilizzata tranquillamente in ambiente desktop ed in ambito server. Una volta verificato il dominio, per avere una sicurezza accettabile sulla validità dell’indirizzo occorre controllare che il server SMTP abbia una mailbox associata all’username estratto dalla mail (in realtà esistono dei server configurati per rispondere tramite un catch-all, in modo da non dare agli spammer indicazione sulla presenza o meno di un certo indirizzo).

Per questo metodo di validazione, e per un’applicazione completa basata su queste classi, rimando ad un altro post.

Di Luca Adamo

Luca Adamo si è laureato con lode in Ingegneria delle Telecomunicazioni all'Università degli studi di Firenze ed è dottorando in Ingegneria Informatica, Multimedialità e Telecomunicazioni, sempre nella stessa facoltà. Le sue competenze tecniche includono lo sviluppo software, sia orientato al web che desktop, in C/C++ e Java (J2EE, J2SE, J2ME), l'amministrazione di macchine Unix-based, la gestione di reti di telecomunicazioni, ed il design di database relazionali.

Una risposta su “Un approccio pure java per la validazione di indirizzi email”

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *