    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 * @since 1.4
    034 */
    035public class EmailValidator implements Serializable {
    036
    037    private static final long serialVersionUID = 1705927040799295880L;
    038
    039    private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
    040    private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]";
    041    private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")";
    042    private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
    043
    044    private static final String EMAIL_REGEX = "^(.+)@(\\S+)$";
    045    private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
    046    private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$";
    047
    048    private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
    049    private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
    050    private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
    051
    052    private static final int MAX_USERNAME_LEN = 64;
    053
    054    /**
    055     * Singleton instance of this class, which
    056     *  doesn't consider local addresses as valid.
    057     */
    058    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false);
    059
    060    /**
    061     * Singleton instance of this class, which
    062     *  doesn't consider local addresses as valid.
    063     */
    064    private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true);
    065
    066    /**
    067     * Singleton instance of this class, which does
    068     *  consider local addresses valid.
    069     */
    070    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false);
    071
    072    /**
    073     * Singleton instance of this class, which does
    074     *  consider local addresses valid.
    075     */
    076    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true);
    077
    078    /**
    079     * Returns the Singleton instance of this validator.
    080     *
    081     * @return singleton instance of this validator.
    082     */
    083    public static EmailValidator getInstance() {
    084        return EMAIL_VALIDATOR;
    085    }
    086
    087    /**
    088     * Returns the Singleton instance of this validator,
    089     *  with local validation as required.
    090     *
    091     * @param allowLocal Should local addresses be considered valid?
    092     * @return singleton instance of this validator
    093     */
    094    public static EmailValidator getInstance(final boolean allowLocal) {
    095        return getInstance(allowLocal, false);
    096    }
    097
    098    /**
    099     * Returns the Singleton instance of this validator,
    100     *  with local validation as required.
    101     *
    102     * @param allowLocal Should local addresses be considered valid?
    103     * @param allowTld Should TLDs be allowed?
    104     * @return singleton instance of this validator
    105     */
    106    public static EmailValidator getInstance(final boolean allowLocal, final boolean allowTld) {
    107        if (allowLocal) {
    108            if (allowTld) {
    109                return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD;
    110            }
    111            return EMAIL_VALIDATOR_WITH_LOCAL;
    112        }
    113        if (allowTld) {
    114            return EMAIL_VALIDATOR_WITH_TLD;
    115        }
    116        return EMAIL_VALIDATOR;
    117    }
    118
    119    private final boolean allowTld;
    120
    121    private final DomainValidator domainValidator;
    122
    123    /**
    124     * Protected constructor for subclasses to use.
    125     *
    126     * @param allowLocal Should local addresses be considered valid?
    127     */
    128    protected EmailValidator(final boolean allowLocal) {
    129        this(allowLocal, false);
    130    }
    131
    132    /**
    133     * Protected constructor for subclasses to use.
    134     *
    135     * @param allowLocal Should local addresses be considered valid?
    136     * @param allowTld Should TLDs be allowed?
    137     */
    138    protected EmailValidator(final boolean allowLocal, final boolean allowTld) {
    139        this.allowTld = allowTld;
    140        this.domainValidator = DomainValidator.getInstance(allowLocal);
    141    }
    142
    143    /**
    144     * constructor for creating instances with the specified domainValidator
    145     *
    146     * @param allowLocal Should local addresses be considered valid?
    147     * @param allowTld Should TLDs be allowed?
    148     * @param domainValidator allow override of the DomainValidator.
    149     * The instance must have the same allowLocal setting.
    150     * @since 1.7
    151     */
    152    public EmailValidator(final boolean allowLocal, final boolean allowTld, final DomainValidator domainValidator) {
    153        this.allowTld = allowTld;
    154        if (domainValidator == null) {
    155            throw new IllegalArgumentException("DomainValidator cannot be null");
    156        }
    157        if (domainValidator.isAllowLocal() != allowLocal) {
    158            throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting");
    159        }
    160        this.domainValidator = domainValidator;
    161    }
    162
    163    /**
    164     * <p>Checks if a field has a valid e-mail address.</p>
    165     *
    166     * @param email The value validation is being performed on.  A <code>null</code>
    167     *              value is considered invalid.
    168     * @return true if the email address is valid.
    169     */
    170    public boolean isValid(final String email) {
    171        if (email == null) {
    172            return false;
    173        }
    174
    175        if (email.endsWith(".")) { // check this first - it's cheap!
    176            return false;
    177        }
    178
    179        // Check the whole email address structure
    180        final Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
    181        if (!emailMatcher.matches()) {
    182            return false;
    183        }
    184
    185        if (!isValidUser(emailMatcher.group(1))) {
    186            return false;
    187        }
    188
    189        if (!isValidDomain(emailMatcher.group(2))) {
    190            return false;
    191        }
    192
    193        return true;
    194    }
    195
    196    /**
    197     * Returns true if the domain component of an email address is valid.
    198     *
    199     * @param domain being validated, may be in IDN format
    200     * @return true if the email address's domain is valid.
    201     */
    202    protected boolean isValidDomain(final String domain) {
    203        // see if domain is an IP address in brackets
    204        final Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
    205
    206        if (ipDomainMatcher.matches()) {
    207            final InetAddressValidator inetAddressValidator =
    208                    InetAddressValidator.getInstance();
    209            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
    210        }
    211        // Domain is symbolic name
    212        if (allowTld) {
    213            return domainValidator.isValid(domain) || !domain.startsWith(".") && domainValidator.isValidTld(domain);
    214        }
    215        return domainValidator.isValid(domain);
    216    }
    217
    218    /**
    219     * Returns true if the user component of an email address is valid.
    220     *
    221     * @param user being validated
    222     * @return true if the user name is valid.
    223     */
    224    protected boolean isValidUser(final String user) {
    225
    226        if (user == null || user.length() > MAX_USERNAME_LEN) {
    227            return false;
    228        }
    229
    230        return USER_PATTERN.matcher(user).matches();
    231    }
    232
    233}