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;
018    
019    import org.apache.commons.validator.routines.InetAddressValidator;
020    import org.apache.oro.text.perl.Perl5Util;
021    
022    import java.util.regex.Matcher;
023    import java.util.regex.Pattern;
024    
025    /**
026     * <p>Perform email validations.</p>
027     * <p>
028     * This class is a Singleton; you can retrieve the instance via the getInstance() method.
029     * </p>
030     * <p>
031     * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
032     * http://javascript.internet.com
033     * </p>
034     * <p>
035     * This implementation is not guaranteed to catch all possible errors in an email address.
036     * For example, an address like nobody@noplace.somedog will pass validator, even though there
037     * is no TLD "somedog"
038     * </p>.
039     *
040     * @version $Revision: 658832 $ $Date: 2008-05-21 21:57:21 +0200 (Mi, 21. Mai 2008) $
041     * @since Validator 1.1
042     * @deprecated Use the new EmailValidator in the routines package. This class
043     * will be removed in a future release.
044     */
045    public class EmailValidator {
046    
047        private static final String SPECIAL_CHARS = "\\000-\\037\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]\\177";
048        private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]";
049        private static final String QUOTED_USER = "(\"[^\"]*\")";
050        private static final String ATOM = VALID_CHARS + '+';
051        private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
052    
053        // Each pattern must be surrounded by /
054        private static final String LEGAL_ASCII_PATTERN = "/^[\\000-\\177]+$/";
055        private static final String EMAIL_PATTERN = "/^(.+)@(.+)$/";
056        private static final String IP_DOMAIN_PATTERN = "^\\[(.*)\\]$";
057        private static final String TLD_PATTERN = "/^([a-zA-Z]+)$/";
058                
059        private static final String USER_PATTERN = "/^\\s*" + WORD + "(\\." + WORD + ")*$/";
060        private static final String DOMAIN_PATTERN = "/^" + ATOM + "(\\." + ATOM + ")*\\s*$/";
061        private static final String ATOM_PATTERN = "/(" + ATOM + ")/";
062    
063        /**
064         * Singleton instance of this class.
065         */
066        private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator();
067    
068        /**
069         * Returns the Singleton instance of this validator.
070         * @return singleton instance of this validator.
071         */
072        public static EmailValidator getInstance() {
073            return EMAIL_VALIDATOR;
074        }
075    
076        /**
077         * Protected constructor for subclasses to use.
078         */
079        protected EmailValidator() {
080            super();
081        }
082    
083        /**
084         * <p>Checks if a field has a valid e-mail address.</p>
085         *
086         * @param email The value validation is being performed on.  A <code>null</code>
087         * value is considered invalid.
088         * @return true if the email address is valid.
089         */
090        public boolean isValid(String email) {
091            return org.apache.commons.validator.routines.EmailValidator.getInstance().isValid(email);
092        }
093    
094        /**
095         * Returns true if the domain component of an email address is valid.
096         * @param domain being validated.
097         * @return true if the email address's domain is valid.
098         */
099        protected boolean isValidDomain(String domain) {
100            boolean symbolic = false;
101    
102            // see if domain is an IP address in brackets
103            Pattern ipDomainPattern = Pattern.compile(IP_DOMAIN_PATTERN);
104            Matcher ipDomainMatcher = ipDomainPattern.matcher(domain);
105    
106            if (ipDomainMatcher.matches()) {
107                InetAddressValidator inetAddressValidator =
108                        InetAddressValidator.getInstance();
109                if (inetAddressValidator.isValid(ipDomainMatcher.group(1))) {
110                    return true;
111                }
112            } else {
113                // Domain is symbolic name
114                Perl5Util domainMatcher = new Perl5Util();
115                symbolic = domainMatcher.match(DOMAIN_PATTERN, domain);
116            }
117    
118            if (symbolic) {
119                if (!isValidSymbolicDomain(domain)) {
120                    return false;
121                }
122            } else {
123                return false;
124            }
125    
126            return true;
127        }
128    
129        /**
130         * Returns true if the user component of an email address is valid.
131         * @param user being validated
132         * @return true if the user name is valid.
133         */
134        protected boolean isValidUser(String user) {
135            Perl5Util userMatcher = new Perl5Util();
136            return userMatcher.match(USER_PATTERN, user);
137        }
138    
139        /**
140         * Validates an IP address. Returns true if valid.
141         * @param ipAddressMatcher Pattren matcher
142         * @return true if the ip address is valid.
143         */
144        protected boolean isValidIpAddress(Perl5Util ipAddressMatcher) {
145            for (int i = 1; i <= 4; i++) {
146                String ipSegment = ipAddressMatcher.group(i);
147                if (ipSegment == null || ipSegment.length() <= 0) {
148                    return false;
149                }
150    
151                int iIpSegment = 0;
152    
153                try {
154                    iIpSegment = Integer.parseInt(ipSegment);
155                } catch(NumberFormatException e) {
156                    return false;
157                }
158    
159                if (iIpSegment > 255) {
160                    return false;
161                }
162    
163            }
164            return true;
165        }
166    
167        /**
168         * Validates a symbolic domain name.  Returns true if it's valid.
169         * @param domain symbolic domain name
170         * @return true if the symbolic domain name is valid.
171         */
172        protected boolean isValidSymbolicDomain(String domain) {
173            String[] domainSegment = new String[10];
174            boolean match = true;
175            int i = 0;
176            Perl5Util atomMatcher = new Perl5Util();
177            while (match) {
178                match = atomMatcher.match(ATOM_PATTERN, domain);
179                if (match) {
180                    domainSegment[i] = atomMatcher.group(1);
181                    int l = domainSegment[i].length() + 1;
182                    domain =
183                            (l >= domain.length())
184                            ? ""
185                            : domain.substring(l);
186    
187                    i++;
188                } 
189            }
190    
191            int len = i;
192            
193            // Make sure there's a host name preceding the domain.
194            if (len < 2) {
195                return false;
196            }
197            
198            // TODO: the tld should be checked against some sort of configurable 
199            // list
200            String tld = domainSegment[len - 1];
201            if (tld.length() > 1) {
202                Perl5Util matchTldPat = new Perl5Util();
203                if (!matchTldPat.match(TLD_PATTERN, tld)) {
204                    return false;
205                }
206            } else {
207                return false;
208            }
209    
210            return true;
211        }
212        /**
213         *   Recursively remove comments, and replace with a single space.  The simpler
214         *   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
215         *   chars in atoms, for example.
216         *   Derived From    Mail::RFC822::Address
217         * @param emailStr The email address
218         * @return address with comments removed.
219        */
220        protected String stripComments(String emailStr)  {
221         String input = emailStr;
222         String result = emailStr;
223         String commentPat = "s/^((?:[^\"\\\\]|\\\\.)*(?:\"(?:[^\"\\\\]|\\\\.)*\"(?:[^\"\\\\]|\111111\\\\.)*)*)\\((?:[^()\\\\]|\\\\.)*\\)/$1 /osx";
224         Perl5Util commentMatcher = new Perl5Util();
225         result = commentMatcher.substitute(commentPat,input);
226         // This really needs to be =~ or Perl5Matcher comparison
227         while (!result.equals(input)) {
228            input = result;
229            result = commentMatcher.substitute(commentPat,input);
230         }
231         return result;
232    
233        }
234    }