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 * This class is a Singleton; you can retrieve the instance via the getInstance() method.
027 * </p>
028 * <p>
029 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
030 * http://javascript.internet.com
031 * </p>
032 * <p>
033 * This implementation is not guaranteed to catch all possible errors in an email address.
034 * For example, an address like nobody@noplace.somedog will pass validator, even though there
035 * is no TLD "somedog"
036 * </p>.
037 *
038 * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Do, 05 Jan 2012) $
039 * @since Validator 1.4
040 */
041public class EmailValidator implements Serializable {
042
043    private static final long serialVersionUID = 1705927040799295880L;
044
045    private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
046    private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]";
047    private static final String QUOTED_USER = "(\"[^\"]*\")";
048    private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
049
050    private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$";
051    private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$";
052    private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
053    private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$";
054
055    private static final Pattern MATCH_ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX);
056    private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
057    private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
058    private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
059
060    private final boolean allowLocal;
061
062    /**
063     * Singleton instance of this class, which
064     *  doesn't consider local addresses as valid.
065     */
066    private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false);
067
068    /**
069     * Singleton instance of this class, which does
070     *  consider local addresses valid.
071     */
072    private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true);
073
074    /**
075     * Returns the Singleton instance of this validator.
076     *
077     * @return singleton instance of this validator.
078     */
079    public static EmailValidator getInstance() {
080        return EMAIL_VALIDATOR;
081    }
082
083    /**
084     * Returns the Singleton instance of this validator,
085     *  with local validation as required.
086     *
087     * @param allowLocal Should local addresses be considered valid?
088     * @return singleton instance of this validator
089     */
090    public static EmailValidator getInstance(boolean allowLocal) {
091        if(allowLocal) {
092           return EMAIL_VALIDATOR_WITH_LOCAL;
093        }
094        return EMAIL_VALIDATOR;
095    }
096
097    /**
098     * Protected constructor for subclasses to use.
099     *
100     * @param allowLocal Should local addresses be considered valid?
101     */
102    protected EmailValidator(boolean allowLocal) {
103        super();
104        this.allowLocal = allowLocal;
105    }
106
107    /**
108     * <p>Checks if a field has a valid e-mail address.</p>
109     *
110     * @param email The value validation is being performed on.  A <code>null</code>
111     *              value is considered invalid.
112     * @return true if the email address is valid.
113     */
114    public boolean isValid(String email) {
115        if (email == null) {
116            return false;
117        }
118
119        Matcher asciiMatcher = MATCH_ASCII_PATTERN.matcher(email);
120        if (!asciiMatcher.matches()) {
121            return false;
122        }
123
124        // Check the whole email address structure
125        Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
126        if (!emailMatcher.matches()) {
127            return false;
128        }
129
130        if (email.endsWith(".")) {
131            return false;
132        }
133
134        if (!isValidUser(emailMatcher.group(1))) {
135            return false;
136        }
137
138        if (!isValidDomain(emailMatcher.group(2))) {
139            return false;
140        }
141
142        return true;
143    }
144
145    /**
146     * Returns true if the domain component of an email address is valid.
147     *
148     * @param domain being validated.
149     * @return true if the email address's domain is valid.
150     */
151    protected boolean isValidDomain(String domain) {
152        // see if domain is an IP address in brackets
153        Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
154
155        if (ipDomainMatcher.matches()) {
156            InetAddressValidator inetAddressValidator =
157                    InetAddressValidator.getInstance();
158            return inetAddressValidator.isValid(ipDomainMatcher.group(1));
159        } else {
160            // Domain is symbolic name
161            DomainValidator domainValidator =
162                    DomainValidator.getInstance(allowLocal);
163            return domainValidator.isValid(domain);
164        }
165    }
166
167    /**
168     * Returns true if the user component of an email address is valid.
169     *
170     * @param user being validated
171     * @return true if the user name is valid.
172     */
173    protected boolean isValidUser(String user) {
174        return USER_PATTERN.matcher(user).matches();
175    }
176
177}