View Javadoc

1   /*
2    * $Id: EmailValidator.java 367161 2006-01-09 02:19:23Z niallp $
3    * $Rev: 367161 $
4    * $Date: 2006-01-09 02:19:23 +0000 (Mon, 09 Jan 2006) $
5    *
6    * ====================================================================
7    * Copyright 2001-2006 The Apache Software Foundation
8    *
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *     http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   */
21  
22  package org.apache.commons.validator;
23  
24  import org.apache.oro.text.perl.Perl5Util;
25  
26  /***
27   * <p>Perform email validations.</p>
28   * <p>
29   * This class is a Singleton; you can retrieve the instance via the getInstance() method.
30   * </p>
31   * <p>
32   * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
33   * http://javascript.internet.com
34   * </p>
35   * <p>
36   * This implementation is not guaranteed to catch all possible errors in an email address.
37   * For example, an address like nobody@noplace.somedog will pass validator, even though there
38   * is no TLD "somedog"
39   * </p>.
40   * @since Validator 1.1
41   */
42  public class EmailValidator {
43  
44      private static final String SPECIAL_CHARS = "//(//)<>@,;:'//////\"//.//[//]";
45      private static final String VALID_CHARS = "[^//s" + SPECIAL_CHARS + "]";
46      private static final String QUOTED_USER = "(\"[^\"]*\")";
47      private static final String ATOM = VALID_CHARS + '+';
48      private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
49  
50      // Each pattern must be surrounded by /
51      private static final String LEGAL_ASCII_PATTERN = "/^[//000-//177]+$/";
52      private static final String EMAIL_PATTERN = "/^(.+)@(.+)$/";
53      private static final String IP_DOMAIN_PATTERN =
54              "/^//[(//d{1,3})[.](//d{1,3})[.](//d{1,3})[.](//d{1,3})//]$/";
55      private static final String TLD_PATTERN = "/^([a-zA-Z]+)$/";
56              
57      private static final String USER_PATTERN = "/^//s*" + WORD + "(//." + WORD + ")*$/";
58      private static final String DOMAIN_PATTERN = "/^" + ATOM + "(//." + ATOM + ")*//s*$/";
59      private static final String ATOM_PATTERN = "/(" + ATOM + ")/";
60  
61      /***
62       * Singleton instance of this class.
63       */
64      private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator();
65  
66      /***
67       * Returns the Singleton instance of this validator.
68       * @return singleton instance of this validator.
69       */
70      public static EmailValidator getInstance() {
71          return EMAIL_VALIDATOR;
72      }
73  
74      /***
75       * Protected constructor for subclasses to use.
76       */
77      protected EmailValidator() {
78          super();
79      }
80  
81      /***
82       * <p>Checks if a field has a valid e-mail address.</p>
83       *
84       * @param email The value validation is being performed on.  A <code>null</code>
85       * value is considered invalid.
86       * @return true if the email address is valid.
87       */
88      public boolean isValid(String email) {
89          if (email == null) {
90              return false;
91          }
92  
93          Perl5Util matchAsciiPat = new Perl5Util();
94          if (!matchAsciiPat.match(LEGAL_ASCII_PATTERN, email)) {
95              return false;
96          }
97  
98          email = stripComments(email);
99  
100         // Check the whole email address structure
101         Perl5Util emailMatcher = new Perl5Util();
102         if (!emailMatcher.match(EMAIL_PATTERN, email)) {
103             return false;
104         }
105 
106         if (email.endsWith(".")) {
107             return false;
108         }
109 
110         if (!isValidUser(emailMatcher.group(1))) {
111             return false;
112         }
113 
114         if (!isValidDomain(emailMatcher.group(2))) {
115             return false;
116         }
117 
118         return true;
119     }
120 
121     /***
122      * Returns true if the domain component of an email address is valid.
123      * @param domain being validatied.
124      * @return true if the email address's domain is valid.
125      */
126     protected boolean isValidDomain(String domain) {
127         boolean symbolic = false;
128         Perl5Util ipAddressMatcher = new Perl5Util();
129 
130         if (ipAddressMatcher.match(IP_DOMAIN_PATTERN, domain)) {
131             if (!isValidIpAddress(ipAddressMatcher)) {
132                 return false;
133             } else {
134                 return true;
135             }
136         } else {
137             // Domain is symbolic name
138             Perl5Util domainMatcher = new Perl5Util();
139             symbolic = domainMatcher.match(DOMAIN_PATTERN, domain);
140         }
141 
142         if (symbolic) {
143             if (!isValidSymbolicDomain(domain)) {
144                 return false;
145             }
146         } else {
147             return false;
148         }
149 
150         return true;
151     }
152 
153     /***
154      * Returns true if the user component of an email address is valid.
155      * @param user being validated
156      * @return true if the user name is valid.
157      */
158     protected boolean isValidUser(String user) {
159         Perl5Util userMatcher = new Perl5Util();
160         return userMatcher.match(USER_PATTERN, user);
161     }
162 
163     /***
164      * Validates an IP address. Returns true if valid.
165      * @param ipAddressMatcher Pattren matcher
166      * @return true if the ip address is valid.
167      */
168     protected boolean isValidIpAddress(Perl5Util ipAddressMatcher) {
169         for (int i = 1; i <= 4; i++) {
170             String ipSegment = ipAddressMatcher.group(i);
171             if (ipSegment == null || ipSegment.length() <= 0) {
172                 return false;
173             }
174 
175             int iIpSegment = 0;
176 
177             try {
178                 iIpSegment = Integer.parseInt(ipSegment);
179             } catch(NumberFormatException e) {
180                 return false;
181             }
182 
183             if (iIpSegment > 255) {
184                 return false;
185             }
186 
187         }
188         return true;
189     }
190 
191     /***
192      * Validates a symbolic domain name.  Returns true if it's valid.
193      * @param domain symbolic domain name
194      * @return true if the symbolic domain name is valid.
195      */
196     protected boolean isValidSymbolicDomain(String domain) {
197         String[] domainSegment = new String[10];
198         boolean match = true;
199         int i = 0;
200         Perl5Util atomMatcher = new Perl5Util();
201         while (match) {
202             match = atomMatcher.match(ATOM_PATTERN, domain);
203             if (match) {
204                 domainSegment[i] = atomMatcher.group(1);
205                 int l = domainSegment[i].length() + 1;
206                 domain =
207                         (l >= domain.length())
208                         ? ""
209                         : domain.substring(l);
210 
211                 i++;
212             } 
213         }
214 
215         int len = i;
216         
217         // Make sure there's a host name preceding the domain.
218         if (len < 2) {
219             return false;
220         }
221         
222         // TODO: the tld should be checked against some sort of configurable 
223         // list
224         String tld = domainSegment[len - 1];
225         if (tld.length() > 1) {
226             Perl5Util matchTldPat = new Perl5Util();
227             if (!matchTldPat.match(TLD_PATTERN, tld)) {
228                 return false;
229             }
230         } else {
231             return false;
232         }
233 
234         return true;
235     }
236     /***
237      *   Recursively remove comments, and replace with a single space.  The simpler
238      *   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
239      *   chars in atoms, for example.
240      *   Derived From    Mail::RFC822::Address
241      * @param emailStr The email address
242      * @return address with comments removed.
243     */
244     protected String stripComments(String emailStr)  {
245      String input = emailStr;
246      String result = emailStr;
247      String commentPat = "s/^((?:[^\"////]|////.)*(?:\"(?:[^\"////]|////.)*\"(?:[^\"////]|\111111////.)*)*)//((?:[^()////]|////.)*//)/$1 /osx";
248      Perl5Util commentMatcher = new Perl5Util();
249      result = commentMatcher.substitute(commentPat,input);
250      // This really needs to be =~ or Perl5Matcher comparison
251      while (!result.equals(input)) {
252         input = result;
253         result = commentMatcher.substitute(commentPat,input);
254      }
255      return result;
256 
257     }
258 }