1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.validator.routines;
18
19 import java.io.Serializable;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22
23 /**
24 * <p>Perform email validations.</p>
25 * <p>
26 * This class is a Singleton; you can retrieve the instance via the getInstance() method.
27 * </p>
28 * <p>
29 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
30 * http://javascript.internet.com
31 * </p>
32 * <p>
33 * This implementation is not guaranteed to catch all possible errors in an email address.
34 * For example, an address like nobody@noplace.somedog will pass validator, even though there
35 * is no TLD "somedog"
36 * </p>.
37 *
38 * @version $Revision: 1227719 $ $Date: 2012-01-05 12:45:51 -0500 (Thu, 05 Jan 2012) $
39 * @since Validator 1.4
40 */
41 public class EmailValidator implements Serializable {
42
43 private static final long serialVersionUID = 1705927040799295880L;
44
45 private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
46 private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]";
47 private static final String QUOTED_USER = "(\"[^\"]*\")";
48 private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
49
50 private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$";
51 private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$";
52 private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
53 private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$";
54
55 private static final Pattern MATCH_ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX);
56 private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
57 private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
58 private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
59
60 private final boolean allowLocal;
61
62 /**
63 * Singleton instance of this class, which
64 * doesn't consider local addresses as valid.
65 */
66 private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false);
67
68 /**
69 * Singleton instance of this class, which does
70 * consider local addresses valid.
71 */
72 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true);
73
74 /**
75 * Returns the Singleton instance of this validator.
76 *
77 * @return singleton instance of this validator.
78 */
79 public static EmailValidator getInstance() {
80 return EMAIL_VALIDATOR;
81 }
82
83 /**
84 * Returns the Singleton instance of this validator,
85 * with local validation as required.
86 *
87 * @param allowLocal Should local addresses be considered valid?
88 * @return singleton instance of this validator
89 */
90 public static EmailValidator getInstance(boolean allowLocal) {
91 if(allowLocal) {
92 return EMAIL_VALIDATOR_WITH_LOCAL;
93 }
94 return EMAIL_VALIDATOR;
95 }
96
97 /**
98 * Protected constructor for subclasses to use.
99 *
100 * @param allowLocal Should local addresses be considered valid?
101 */
102 protected EmailValidator(boolean allowLocal) {
103 super();
104 this.allowLocal = allowLocal;
105 }
106
107 /**
108 * <p>Checks if a field has a valid e-mail address.</p>
109 *
110 * @param email The value validation is being performed on. A <code>null</code>
111 * value is considered invalid.
112 * @return true if the email address is valid.
113 */
114 public boolean isValid(String email) {
115 if (email == null) {
116 return false;
117 }
118
119 Matcher asciiMatcher = MATCH_ASCII_PATTERN.matcher(email);
120 if (!asciiMatcher.matches()) {
121 return false;
122 }
123
124 // Check the whole email address structure
125 Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
126 if (!emailMatcher.matches()) {
127 return false;
128 }
129
130 if (email.endsWith(".")) {
131 return false;
132 }
133
134 if (!isValidUser(emailMatcher.group(1))) {
135 return false;
136 }
137
138 if (!isValidDomain(emailMatcher.group(2))) {
139 return false;
140 }
141
142 return true;
143 }
144
145 /**
146 * Returns true if the domain component of an email address is valid.
147 *
148 * @param domain being validated.
149 * @return true if the email address's domain is valid.
150 */
151 protected boolean isValidDomain(String domain) {
152 // see if domain is an IP address in brackets
153 Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
154
155 if (ipDomainMatcher.matches()) {
156 InetAddressValidator inetAddressValidator =
157 InetAddressValidator.getInstance();
158 return inetAddressValidator.isValid(ipDomainMatcher.group(1));
159 } else {
160 // Domain is symbolic name
161 DomainValidator domainValidator =
162 DomainValidator.getInstance(allowLocal);
163 return domainValidator.isValid(domain);
164 }
165 }
166
167 /**
168 * Returns true if the user component of an email address is valid.
169 *
170 * @param user being validated
171 * @return true if the user name is valid.
172 */
173 protected boolean isValidUser(String user) {
174 return USER_PATTERN.matcher(user).matches();
175 }
176
177 }