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