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