EmailValidator.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.validator.routines;

  18. import java.io.Serializable;
  19. import java.util.regex.Matcher;
  20. import java.util.regex.Pattern;

  21. /**
  22.  * <p>Perform email validations.</p>
  23.  * <p>
  24.  * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
  25.  * https://javascript.internet.com
  26.  * </p>
  27.  * <p>
  28.  * This implementation is not guaranteed to catch all possible errors in an email address.
  29.  * </p>.
  30.  *
  31.  * @since 1.4
  32.  */
  33. public class EmailValidator implements Serializable {

  34.     private static final long serialVersionUID = 1705927040799295880L;

  35.     private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
  36.     private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]";
  37.     private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")";
  38.     private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";

  39.     private static final String EMAIL_REGEX = "^(.+)@(\\S+)$";
  40.     private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
  41.     private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$";

  42.     private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
  43.     private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
  44.     private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);

  45.     private static final int MAX_USERNAME_LEN = 64;

  46.     /**
  47.      * Singleton instance of this class, which
  48.      *  doesn't consider local addresses as valid.
  49.      */
  50.     private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false);

  51.     /**
  52.      * Singleton instance of this class, which
  53.      *  doesn't consider local addresses as valid.
  54.      */
  55.     private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true);

  56.     /**
  57.      * Singleton instance of this class, which does
  58.      *  consider local addresses valid.
  59.      */
  60.     private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false);

  61.     /**
  62.      * Singleton instance of this class, which does
  63.      *  consider local addresses valid.
  64.      */
  65.     private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true);

  66.     /**
  67.      * Returns the Singleton instance of this validator.
  68.      *
  69.      * @return singleton instance of this validator.
  70.      */
  71.     public static EmailValidator getInstance() {
  72.         return EMAIL_VALIDATOR;
  73.     }

  74.     /**
  75.      * Returns the Singleton instance of this validator,
  76.      *  with local validation as required.
  77.      *
  78.      * @param allowLocal Should local addresses be considered valid?
  79.      * @return singleton instance of this validator
  80.      */
  81.     public static EmailValidator getInstance(final boolean allowLocal) {
  82.         return getInstance(allowLocal, false);
  83.     }

  84.     /**
  85.      * Returns the Singleton instance of this validator,
  86.      *  with local validation as required.
  87.      *
  88.      * @param allowLocal Should local addresses be considered valid?
  89.      * @param allowTld Should TLDs be allowed?
  90.      * @return singleton instance of this validator
  91.      */
  92.     public static EmailValidator getInstance(final boolean allowLocal, final boolean allowTld) {
  93.         if (allowLocal) {
  94.             if (allowTld) {
  95.                 return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD;
  96.             }
  97.             return EMAIL_VALIDATOR_WITH_LOCAL;
  98.         }
  99.         if (allowTld) {
  100.             return EMAIL_VALIDATOR_WITH_TLD;
  101.         }
  102.         return EMAIL_VALIDATOR;
  103.     }

  104.     private final boolean allowTld;

  105.     private final DomainValidator domainValidator;

  106.     /**
  107.      * Protected constructor for subclasses to use.
  108.      *
  109.      * @param allowLocal Should local addresses be considered valid?
  110.      */
  111.     protected EmailValidator(final boolean allowLocal) {
  112.         this(allowLocal, false);
  113.     }

  114.     /**
  115.      * Protected constructor for subclasses to use.
  116.      *
  117.      * @param allowLocal Should local addresses be considered valid?
  118.      * @param allowTld Should TLDs be allowed?
  119.      */
  120.     protected EmailValidator(final boolean allowLocal, final boolean allowTld) {
  121.         this.allowTld = allowTld;
  122.         this.domainValidator = DomainValidator.getInstance(allowLocal);
  123.     }

  124.     /**
  125.      * constructor for creating instances with the specified domainValidator
  126.      *
  127.      * @param allowLocal Should local addresses be considered valid?
  128.      * @param allowTld Should TLDs be allowed?
  129.      * @param domainValidator allow override of the DomainValidator.
  130.      * The instance must have the same allowLocal setting.
  131.      * @since 1.7
  132.      */
  133.     public EmailValidator(final boolean allowLocal, final boolean allowTld, final DomainValidator domainValidator) {
  134.         this.allowTld = allowTld;
  135.         if (domainValidator == null) {
  136.             throw new IllegalArgumentException("DomainValidator cannot be null");
  137.         }
  138.         if (domainValidator.isAllowLocal() != allowLocal) {
  139.             throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting");
  140.         }
  141.         this.domainValidator = domainValidator;
  142.     }

  143.     /**
  144.      * <p>Checks if a field has a valid e-mail address.</p>
  145.      *
  146.      * @param email The value validation is being performed on.  A {@code null}
  147.      *              value is considered invalid.
  148.      * @return true if the email address is valid.
  149.      */
  150.     public boolean isValid(final String email) {
  151.         if (email == null) {
  152.             return false;
  153.         }
  154.         if (email.endsWith(".")) { // check this first - it's cheap!
  155.             return false;
  156.         }
  157.         // Check the whole email address structure
  158.         final Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
  159.         if (!emailMatcher.matches()) {
  160.             return false;
  161.         }
  162.         if (!isValidUser(emailMatcher.group(1))) {
  163.             return false;
  164.         }
  165.         if (!isValidDomain(emailMatcher.group(2))) {
  166.             return false;
  167.         }
  168.         return true;
  169.     }

  170.     /**
  171.      * Returns true if the domain component of an email address is valid.
  172.      *
  173.      * @param domain being validated, may be in IDN format
  174.      * @return true if the email address's domain is valid.
  175.      */
  176.     protected boolean isValidDomain(final String domain) {
  177.         // see if domain is an IP address in brackets
  178.         final Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);

  179.         if (ipDomainMatcher.matches()) {
  180.             final InetAddressValidator inetAddressValidator =
  181.                     InetAddressValidator.getInstance();
  182.             return inetAddressValidator.isValid(ipDomainMatcher.group(1));
  183.         }
  184.         // Domain is symbolic name
  185.         if (allowTld) {
  186.             return domainValidator.isValid(domain) || !domain.startsWith(".") && domainValidator.isValidTld(domain);
  187.         }
  188.         return domainValidator.isValid(domain);
  189.     }

  190.     /**
  191.      * Returns true if the user component of an email address is valid.
  192.      *
  193.      * @param user being validated
  194.      * @return true if the user name is valid.
  195.      */
  196.     protected boolean isValidUser(final String user) {

  197.         if (user == null || user.length() > MAX_USERNAME_LEN) {
  198.             return false;
  199.         }

  200.         return USER_PATTERN.matcher(user).matches();
  201.     }

  202. }