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: 1649927 $
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*?(.+)@(.+?)\\s*$";
046    private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
047    private static final String USER_REGEX = "^\\s*" + 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 final boolean allowLocal;
054
055    /**
056     * Singleton instance of this class, which
057     *  doesn't consider local addresses as valid.
058     */
059    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false);
060
061    /**
062     * Singleton instance of this class, which does
063     *  consider local addresses valid.
064     */
065    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true);
066
067    /**
068     * Returns the Singleton instance of this validator.
069     *
070     * @return singleton instance of this validator.
071     */
072    public static EmailValidator getInstance() {
073        return EMAIL_VALIDATOR;
074    }
075
076    /**
077     * Returns the Singleton instance of this validator,
078     *  with local validation as required.
079     *
080     * @param allowLocal Should local addresses be considered valid?
081     * @return singleton instance of this validator
082     */
083    public static EmailValidator getInstance(boolean allowLocal) {
084        if(allowLocal) {
085           return EMAIL_VALIDATOR_WITH_LOCAL;
086        }
087        return EMAIL_VALIDATOR;
088    }
089
090    /**
091     * Protected constructor for subclasses to use.
092     *
093     * @param allowLocal Should local addresses be considered valid?
094     */
095    protected EmailValidator(boolean allowLocal) {
096        super();
097        this.allowLocal = allowLocal;
098    }
099
100    /**
101     * <p>Checks if a field has a valid e-mail address.</p>
102     *
103     * @param email The value validation is being performed on.  A <code>null</code>
104     *              value is considered invalid.
105     * @return true if the email address is valid.
106     */
107    public boolean isValid(String email) {
108        if (email == null) {
109            return false;
110        }
111
112        if (email.endsWith(".")) { // check this first - it's cheap!
113            return false;
114        }
115
116        // Check the whole email address structure
117        Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
118        if (!emailMatcher.matches()) {
119            return false;
120        }
121
122        if (!isValidUser(emailMatcher.group(1))) {
123            return false;
124        }
125
126        if (!isValidDomain(emailMatcher.group(2))) {
127            return false;
128        }
129
130        return true;
131    }
132
133    /**
134     * Returns true if the domain component of an email address is valid.
135     *
136     * @param domain being validated, may be in IDN format
137     * @return true if the email address's domain is valid.
138     */
139    protected boolean isValidDomain(String domain) {
140        // see if domain is an IP address in brackets
141        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
142
143        if (ipDomainMatcher.matches()) {
144            InetAddressValidator inetAddressValidator =
145                    InetAddressValidator.getInstance();
146            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
147        }
148        // Domain is symbolic name
149        DomainValidator domainValidator =
150                DomainValidator.getInstance(allowLocal);
151        return domainValidator.isValid(domain) ||
152                domainValidator.isValidTld(domain);
153    }
154
155    /**
156     * Returns true if the user component of an email address is valid.
157     *
158     * @param user being validated
159     * @return true if the user name is valid.
160     */
161    protected boolean isValidUser(String user) {
162        return USER_PATTERN.matcher(user).matches();
163    }
164
165}