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 * https://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 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
27 * https://javascript.internet.com
28 * </p>
29 * <p>
30 * This implementation is not guaranteed to catch all possible errors in an email address.
31 * </p>.
32 *
33 * @since 1.4
34 */
35 public class EmailValidator implements Serializable {
36
37 private static final long serialVersionUID = 1705927040799295880L;
38
39 private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]";
40 private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]";
41 private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")";
42 private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
43
44 private static final String EMAIL_REGEX = "^(.+)@(\\S+)$";
45 private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$";
46 private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$";
47
48 private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
49 private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX);
50 private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX);
51
52 private static final int MAX_USERNAME_LEN = 64;
53
54 /**
55 * Singleton instance of this class, which
56 * doesn't consider local addresses as valid.
57 */
58 private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false);
59
60 /**
61 * Singleton instance of this class, which
62 * doesn't consider local addresses as valid.
63 */
64 private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true);
65
66 /**
67 * Singleton instance of this class, which does
68 * consider local addresses valid.
69 */
70 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false);
71
72 /**
73 * Singleton instance of this class, which does
74 * consider local addresses valid.
75 */
76 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true);
77
78 /**
79 * Returns the Singleton instance of this validator.
80 *
81 * @return singleton instance of this validator.
82 */
83 public static EmailValidator getInstance() {
84 return EMAIL_VALIDATOR;
85 }
86
87 /**
88 * Returns the Singleton instance of this validator,
89 * with local validation as required.
90 *
91 * @param allowLocal Should local addresses be considered valid?
92 * @return singleton instance of this validator
93 */
94 public static EmailValidator getInstance(final boolean allowLocal) {
95 return getInstance(allowLocal, false);
96 }
97
98 /**
99 * Returns the Singleton instance of this validator, with local validation as required.
100 *
101 * @param allowLocal Should local addresses be considered valid?
102 * @param allowTld Should TLDs be allowed?
103 * @return singleton instance of this validator
104 */
105 public static EmailValidator getInstance(final boolean allowLocal, final boolean allowTld) {
106 if (allowLocal) {
107 if (allowTld) {
108 return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD;
109 }
110 return EMAIL_VALIDATOR_WITH_LOCAL;
111 }
112 if (allowTld) {
113 return EMAIL_VALIDATOR_WITH_TLD;
114 }
115 return EMAIL_VALIDATOR;
116 }
117
118 /**
119 * Whether to allow TLDs.
120 */
121 private final boolean allowTld;
122
123 /**
124 * The domain validator.
125 */
126 private final DomainValidator domainValidator;
127
128 /**
129 * Protected constructor for subclasses to use.
130 *
131 * @param allowLocal Should local addresses be considered valid?
132 */
133 protected EmailValidator(final boolean allowLocal) {
134 this(allowLocal, false);
135 }
136
137 /**
138 * Protected constructor for subclasses to use.
139 *
140 * @param allowLocal Should local addresses be considered valid?
141 * @param allowTld Should TLDs be allowed?
142 */
143 protected EmailValidator(final boolean allowLocal, final boolean allowTld) {
144 this.allowTld = allowTld;
145 this.domainValidator = DomainValidator.getInstance(allowLocal);
146 }
147
148 /**
149 * constructor for creating instances with the specified domainValidator
150 *
151 * @param allowLocal Should local addresses be considered valid?
152 * @param allowTld Should TLDs be allowed?
153 * @param domainValidator allow override of the DomainValidator.
154 * The instance must have the same allowLocal setting.
155 * @since 1.7
156 */
157 public EmailValidator(final boolean allowLocal, final boolean allowTld, final DomainValidator domainValidator) {
158 this.allowTld = allowTld;
159 if (domainValidator == null) {
160 throw new IllegalArgumentException("DomainValidator cannot be null");
161 }
162 if (domainValidator.isAllowLocal() != allowLocal) {
163 throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting");
164 }
165 this.domainValidator = domainValidator;
166 }
167
168 /**
169 * <p>Checks if a field has a valid e-mail address.</p>
170 *
171 * @param email The value validation is being performed on. A {@code null}
172 * value is considered invalid.
173 * @return true if the email address is valid.
174 */
175 public boolean isValid(final String email) {
176 if (email == null || email.endsWith(".")) { // check this first - it's cheap!
177 return false;
178 }
179 // Check the whole email address structure
180 final Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
181 if (!emailMatcher.matches()) {
182 return false;
183 }
184 if (!isValidUser(emailMatcher.group(1))) {
185 return false;
186 }
187 if (!isValidDomain(emailMatcher.group(2))) {
188 return false;
189 }
190 return true;
191 }
192
193 /**
194 * Returns true if the domain component of an email address is valid.
195 *
196 * @param domain being validated, may be in IDN format
197 * @return true if the email address's domain is valid.
198 */
199 protected boolean isValidDomain(final String domain) {
200 // see if domain is an IP address in brackets
201 final Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain);
202
203 if (ipDomainMatcher.matches()) {
204 final InetAddressValidator inetAddressValidator =
205 InetAddressValidator.getInstance();
206 return inetAddressValidator.isValid(ipDomainMatcher.group(1));
207 }
208 // Domain is symbolic name
209 if (allowTld) {
210 return domainValidator.isValid(domain) || !domain.startsWith(".") && domainValidator.isValidTld(domain);
211 }
212 return domainValidator.isValid(domain);
213 }
214
215 /**
216 * Returns true if the user component of an email address is valid.
217 *
218 * @param user being validated
219 * @return true if the username is valid.
220 */
221 protected boolean isValidUser(final String user) {
222
223 if (user == null || user.length() > MAX_USERNAME_LEN) {
224 return false;
225 }
226
227 return USER_PATTERN.matcher(user).matches();
228 }
229
230 }