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: 1723573 $
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 static final int MAX_USERNAME_LEN = 64;
054
055    private final boolean allowLocal;
056    private final boolean allowTld;
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    /**
078     * Singleton instance of this class, which does
079     *  consider local addresses valid.
080     */
081    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true);
082
083    /**
084     * Returns the Singleton instance of this validator.
085     *
086     * @return singleton instance of this validator.
087     */
088    public static EmailValidator getInstance() {
089        return EMAIL_VALIDATOR;
090    }
091
092    /**
093     * Returns the Singleton instance of this validator,
094     *  with local validation as required.
095     *
096     * @param allowLocal Should local addresses be considered valid?
097     * @param allowTld Should TLDs be allowed?
098     * @return singleton instance of this validator
099     */
100    public static EmailValidator getInstance(boolean allowLocal, boolean allowTld) {
101        if(allowLocal) {
102            if (allowTld) {
103                return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD;
104            } else {
105                return EMAIL_VALIDATOR_WITH_LOCAL;
106            }
107        } else {
108            if (allowTld) {
109                return EMAIL_VALIDATOR_WITH_TLD;
110            } else {
111                return EMAIL_VALIDATOR;
112            }
113        }
114    }
115
116    /**
117     * Returns the Singleton instance of this validator,
118     *  with local validation as required.
119     *
120     * @param allowLocal Should local addresses be considered valid?
121     * @return singleton instance of this validator
122     */
123    public static EmailValidator getInstance(boolean allowLocal) {
124        return getInstance(allowLocal, false);
125    }
126
127    /**
128     * Protected constructor for subclasses to use.
129     *
130     * @param allowLocal Should local addresses be considered valid?
131     * @param allowTld Should TLDs be allowed?
132     */
133    protected EmailValidator(boolean allowLocal, boolean allowTld) {
134        super();
135        this.allowLocal = allowLocal;
136        this.allowTld = allowTld;
137    }
138
139    /**
140     * Protected constructor for subclasses to use.
141     *
142     * @param allowLocal Should local addresses be considered valid?
143     */
144    protected EmailValidator(boolean allowLocal) {
145        super();
146        this.allowLocal = allowLocal;
147        this.allowTld = false;
148    }
149
150    /**
151     * <p>Checks if a field has a valid e-mail address.</p>
152     *
153     * @param email The value validation is being performed on.  A <code>null</code>
154     *              value is considered invalid.
155     * @return true if the email address is valid.
156     */
157    public boolean isValid(String email) {
158        if (email == null) {
159            return false;
160        }
161
162        if (email.endsWith(".")) { // check this first - it's cheap!
163            return false;
164        }
165
166        // Check the whole email address structure
167        Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
168        if (!emailMatcher.matches()) {
169            return false;
170        }
171
172        if (!isValidUser(emailMatcher.group(1))) {
173            return false;
174        }
175
176        if (!isValidDomain(emailMatcher.group(2))) {
177            return false;
178        }
179
180        return true;
181    }
182
183    /**
184     * Returns true if the domain component of an email address is valid.
185     *
186     * @param domain being validated, may be in IDN format
187     * @return true if the email address's domain is valid.
188     */
189    protected boolean isValidDomain(String domain) {
190        // see if domain is an IP address in brackets
191        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
192
193        if (ipDomainMatcher.matches()) {
194            InetAddressValidator inetAddressValidator =
195                    InetAddressValidator.getInstance();
196            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
197        }
198        // Domain is symbolic name
199        DomainValidator domainValidator =
200                DomainValidator.getInstance(allowLocal);
201        if (allowTld) {
202            return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain));
203        } else {
204            return domainValidator.isValid(domain);
205        }
206    }
207
208    /**
209     * Returns true if the user component of an email address is valid.
210     *
211     * @param user being validated
212     * @return true if the user name is valid.
213     */
214    protected boolean isValidUser(String user) {
215        
216        if (user == null || user.length() > MAX_USERNAME_LEN) {
217            return false;
218        }
219        
220        return USER_PATTERN.matcher(user).matches();
221    }
222
223}