    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.validator.routines;
    018
    019import java.io.Serializable;
    020import java.util.regex.Matcher;
    021import java.util.regex.Pattern;
    022
    023/**
    024 * <p>Perform email validations.</p>
    025 * <p>
    026 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
    027 * http://javascript.internet.com
    028 * </p>
    029 * <p>
    030 * This implementation is not guaranteed to catch all possible errors in an email address.
    031 * </p>.
    032 *
    033 * @version $Revision$
    034 * @since Validator 1.4
    035 */
    036public class EmailValidator implements Serializable {
    037
    038    private static final long serialVersionUID = 1705927040799295880L;
    039
    040    private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
    041    private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]";
    042    private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")";
    043    private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
    044
    045    private static final String EMAIL_REGEX = "^(.+)@(\\S+)$";
    046    private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
    047    private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$";
    048
    049    private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
    050    private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
    051    private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
    052
    053    private static final int MAX_USERNAME_LEN = 64;
    054
    055    private final boolean allowTld;
    056
    057    /**
    058     * Singleton instance of this class, which
    059     *  doesn't consider local addresses as valid.
    060     */
    061    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false);
    062
    063    /**
    064     * Singleton instance of this class, which
    065     *  doesn't consider local addresses as valid.
    066     */
    067    private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true);
    068
    069    /**
    070     * Singleton instance of this class, which does
    071     *  consider local addresses valid.
    072     */
    073    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false);
    074
    075
    076    /**
    077     * Singleton instance of this class, which does
    078     *  consider local addresses valid.
    079     */
    080    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true);
    081
    082    private final DomainValidator domainValidator;
    083
    084    /**
    085     * Returns the Singleton instance of this validator.
    086     *
    087     * @return singleton instance of this validator.
    088     */
    089    public static EmailValidator getInstance() {
    090        return EMAIL_VALIDATOR;
    091    }
    092
    093    /**
    094     * Returns the Singleton instance of this validator,
    095     *  with local validation as required.
    096     *
    097     * @param allowLocal Should local addresses be considered valid?
    098     * @param allowTld Should TLDs be allowed?
    099     * @return singleton instance of this validator
    100     */
    101    public static EmailValidator getInstance(boolean allowLocal, boolean allowTld) {
    102        if(allowLocal) {
    103            if (allowTld) {
    104                return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD;
    105            } else {
    106                return EMAIL_VALIDATOR_WITH_LOCAL;
    107            }
    108        } else {
    109            if (allowTld) {
    110                return EMAIL_VALIDATOR_WITH_TLD;
    111            } else {
    112                return EMAIL_VALIDATOR;
    113            }
    114        }
    115    }
    116
    117    /**
    118     * Returns the Singleton instance of this validator,
    119     *  with local validation as required.
    120     *
    121     * @param allowLocal Should local addresses be considered valid?
    122     * @return singleton instance of this validator
    123     */
    124    public static EmailValidator getInstance(boolean allowLocal) {
    125        return getInstance(allowLocal, false);
    126    }
    127
    128    /**
    129     * constructor for creating instances with the specified domainValidator
    130     *
    131     * @param allowLocal Should local addresses be considered valid?
    132     * @param allowTld Should TLDs be allowed?
    133     * @param domainValidator allow override of the DomainValidator.
    134     * The instance must have the same allowLocal setting.
    135     * @since 1.7
    136     */
    137    public EmailValidator(boolean allowLocal, boolean allowTld, DomainValidator domainValidator) {
    138        super();
    139        this.allowTld = allowTld;
    140        if (domainValidator == null) {
    141            throw new IllegalArgumentException("DomainValidator cannot be null");
    142        } else {
    143            if (domainValidator.isAllowLocal() != allowLocal) {
    144                throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting");
    145            }
    146            this.domainValidator = domainValidator;
    147        }
    148    }
    149
    150    /**
    151     * Protected constructor for subclasses to use.
    152     *
    153     * @param allowLocal Should local addresses be considered valid?
    154     * @param allowTld Should TLDs be allowed?
    155     */
    156    protected EmailValidator(boolean allowLocal, boolean allowTld) {
    157        this.allowTld = allowTld;
    158        this.domainValidator = DomainValidator.getInstance(allowLocal);
    159    }
    160
    161    /**
    162     * Protected constructor for subclasses to use.
    163     *
    164     * @param allowLocal Should local addresses be considered valid?
    165     */
    166    protected EmailValidator(boolean allowLocal) {
    167        this(allowLocal, false);
    168    }
    169
    170    /**
    171     * <p>Checks if a field has a valid e-mail address.</p>
    172     *
    173     * @param email The value validation is being performed on.  A <code>null</code>
    174     *              value is considered invalid.
    175     * @return true if the email address is valid.
    176     */
    177    public boolean isValid(String email) {
    178        if (email == null) {
    179            return false;
    180        }
    181
    182        if (email.endsWith(".")) { // check this first - it's cheap!
    183            return false;
    184        }
    185
    186        // Check the whole email address structure
    187        Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
    188        if (!emailMatcher.matches()) {
    189            return false;
    190        }
    191
    192        if (!isValidUser(emailMatcher.group(1))) {
    193            return false;
    194        }
    195
    196        if (!isValidDomain(emailMatcher.group(2))) {
    197            return false;
    198        }
    199
    200        return true;
    201    }
    202
    203    /**
    204     * Returns true if the domain component of an email address is valid.
    205     *
    206     * @param domain being validated, may be in IDN format
    207     * @return true if the email address's domain is valid.
    208     */
    209    protected boolean isValidDomain(String domain) {
    210        // see if domain is an IP address in brackets
    211        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
    212
    213        if (ipDomainMatcher.matches()) {
    214            InetAddressValidator inetAddressValidator =
    215                    InetAddressValidator.getInstance();
    216            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
    217        }
    218        // Domain is symbolic name
    219        if (allowTld) {
    220            return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain));
    221        } else {
    222            return domainValidator.isValid(domain);
    223        }
    224    }
    225
    226    /**
    227     * Returns true if the user component of an email address is valid.
    228     *
    229     * @param user being validated
    230     * @return true if the user name is valid.
    231     */
    232    protected boolean isValidUser(String user) {
    233
    234        if (user == null || user.length() > MAX_USERNAME_LEN) {
    235            return false;
    236        }
    237
    238        return USER_PATTERN.matcher(user).matches();
    239    }
    240
    241}