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     */
017    package org.apache.commons.validator.routines;
018    
019    import java.io.Serializable;
020    import java.util.regex.Matcher;
021    import 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: 594904 $ $Date: 2007-11-14 15:30:27 +0100 (Mi, 14. Nov 2007) $
039     * @since Validator 1.4
040     */
041    public class EmailValidator implements Serializable {
042    
043        private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
044        private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]";
045        private static final String QUOTED_USER = "(\"[^\"]*\")";
046        private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
047    
048        private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$";
049        private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$";
050        private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
051        private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$";
052    
053        private static final Pattern MATCH_ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX);
054        private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
055        private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
056        private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
057    
058        /**
059         * Singleton instance of this class.
060         */
061        private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator();
062    
063        /**
064         * Returns the Singleton instance of this validator.
065         *
066         * @return singleton instance of this validator.
067         */
068        public static EmailValidator getInstance() {
069            return EMAIL_VALIDATOR;
070        }
071    
072        /**                                       
073         * Protected constructor for subclasses to use.
074         */
075        protected EmailValidator() {
076            super();
077        }
078    
079        /**
080         * <p>Checks if a field has a valid e-mail address.</p>
081         *
082         * @param email The value validation is being performed on.  A <code>null</code>
083         *              value is considered invalid.
084         * @return true if the email address is valid.
085         */
086        public boolean isValid(String email) {
087            if (email == null) {
088                return false;
089            }
090    
091            Matcher asciiMatcher = MATCH_ASCII_PATTERN.matcher(email);
092            if (!asciiMatcher.matches()) {
093                return false;
094            }
095    
096            // Check the whole email address structure
097            Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
098            if (!emailMatcher.matches()) {
099                return false;
100            }
101    
102            if (email.endsWith(".")) {
103                return false;
104            }
105    
106            if (!isValidUser(emailMatcher.group(1))) {
107                return false;
108            }
109    
110            if (!isValidDomain(emailMatcher.group(2))) {
111                return false;
112            }
113    
114            return true;
115        }
116    
117        /**
118         * Returns true if the domain component of an email address is valid.
119         *
120         * @param domain being validated.
121         * @return true if the email address's domain is valid.
122         */
123        protected boolean isValidDomain(String domain) {
124            // see if domain is an IP address in brackets
125            Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
126    
127            if (ipDomainMatcher.matches()) {
128                InetAddressValidator inetAddressValidator =
129                        InetAddressValidator.getInstance();
130                return inetAddressValidator.isValid(ipDomainMatcher.group(1));
131            } else {
132                // Domain is symbolic name
133                DomainValidator domainValidator =
134                        DomainValidator.getInstance();
135                return domainValidator.isValid(domain);
136            }
137        }
138    
139        /**
140         * Returns true if the user component of an email address is valid.
141         *
142         * @param user being validated
143         * @return true if the user name is valid.
144         */
145        protected boolean isValidUser(String user) {
146            return USER_PATTERN.matcher(user).matches();
147        }
148    
149    }