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