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 */ 017package org.apache.commons.validator.routines; 018 019import java.io.Serializable; 020import java.util.regex.Matcher; 021import java.util.regex.Pattern; 022 023/** 024 * <p>Perform email validations.</p> 025 * <p> 026 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a> 027 * http://javascript.internet.com 028 * </p> 029 * <p> 030 * This implementation is not guaranteed to catch all possible errors in an email address. 031 * </p>. 032 * 033 * @version $Revision$ 034 * @since Validator 1.4 035 */ 036public class EmailValidator implements Serializable { 037 038 private static final long serialVersionUID = 1705927040799295880L; 039 040 private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; 041 private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]"; 042 private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")"; 043 private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; 044 045 private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; 046 private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; 047 private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$"; 048 049 private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); 050 private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); 051 private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); 052 053 private static final int MAX_USERNAME_LEN = 64; 054 055 private final boolean allowTld; 056 057 /** 058 * Singleton instance of this class, which 059 * doesn't consider local addresses as valid. 060 */ 061 private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false); 062 063 /** 064 * Singleton instance of this class, which 065 * doesn't consider local addresses as valid. 066 */ 067 private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true); 068 069 /** 070 * Singleton instance of this class, which does 071 * consider local addresses valid. 072 */ 073 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false); 074 075 076 /** 077 * Singleton instance of this class, which does 078 * consider local addresses valid. 079 */ 080 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD = new EmailValidator(true, true); 081 082 private final DomainValidator domainValidator; 083 084 /** 085 * Returns the Singleton instance of this validator. 086 * 087 * @return singleton instance of this validator. 088 */ 089 public static EmailValidator getInstance() { 090 return EMAIL_VALIDATOR; 091 } 092 093 /** 094 * Returns the Singleton instance of this validator, 095 * with local validation as required. 096 * 097 * @param allowLocal Should local addresses be considered valid? 098 * @param allowTld Should TLDs be allowed? 099 * @return singleton instance of this validator 100 */ 101 public static EmailValidator getInstance(boolean allowLocal, boolean allowTld) { 102 if(allowLocal) { 103 if (allowTld) { 104 return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD; 105 } else { 106 return EMAIL_VALIDATOR_WITH_LOCAL; 107 } 108 } else { 109 if (allowTld) { 110 return EMAIL_VALIDATOR_WITH_TLD; 111 } else { 112 return EMAIL_VALIDATOR; 113 } 114 } 115 } 116 117 /** 118 * Returns the Singleton instance of this validator, 119 * with local validation as required. 120 * 121 * @param allowLocal Should local addresses be considered valid? 122 * @return singleton instance of this validator 123 */ 124 public static EmailValidator getInstance(boolean allowLocal) { 125 return getInstance(allowLocal, false); 126 } 127 128 /** 129 * constructor for creating instances with the specified domainValidator 130 * 131 * @param allowLocal Should local addresses be considered valid? 132 * @param allowTld Should TLDs be allowed? 133 * @param domainValidator allow override of the DomainValidator. 134 * The instance must have the same allowLocal setting. 135 * @since 1.7 136 */ 137 public EmailValidator(boolean allowLocal, boolean allowTld, DomainValidator domainValidator) { 138 super(); 139 this.allowTld = allowTld; 140 if (domainValidator == null) { 141 throw new IllegalArgumentException("DomainValidator cannot be null"); 142 } else { 143 if (domainValidator.isAllowLocal() != allowLocal) { 144 throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting"); 145 } 146 this.domainValidator = domainValidator; 147 } 148 } 149 150 /** 151 * Protected constructor for subclasses to use. 152 * 153 * @param allowLocal Should local addresses be considered valid? 154 * @param allowTld Should TLDs be allowed? 155 */ 156 protected EmailValidator(boolean allowLocal, boolean allowTld) { 157 this.allowTld = allowTld; 158 this.domainValidator = DomainValidator.getInstance(allowLocal); 159 } 160 161 /** 162 * Protected constructor for subclasses to use. 163 * 164 * @param allowLocal Should local addresses be considered valid? 165 */ 166 protected EmailValidator(boolean allowLocal) { 167 this(allowLocal, false); 168 } 169 170 /** 171 * <p>Checks if a field has a valid e-mail address.</p> 172 * 173 * @param email The value validation is being performed on. A <code>null</code> 174 * value is considered invalid. 175 * @return true if the email address is valid. 176 */ 177 public boolean isValid(String email) { 178 if (email == null) { 179 return false; 180 } 181 182 if (email.endsWith(".")) { // check this first - it's cheap! 183 return false; 184 } 185 186 // Check the whole email address structure 187 Matcher emailMatcher = EMAIL_PATTERN.matcher(email); 188 if (!emailMatcher.matches()) { 189 return false; 190 } 191 192 if (!isValidUser(emailMatcher.group(1))) { 193 return false; 194 } 195 196 if (!isValidDomain(emailMatcher.group(2))) { 197 return false; 198 } 199 200 return true; 201 } 202 203 /** 204 * Returns true if the domain component of an email address is valid. 205 * 206 * @param domain being validated, may be in IDN format 207 * @return true if the email address's domain is valid. 208 */ 209 protected boolean isValidDomain(String domain) { 210 // see if domain is an IP address in brackets 211 Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); 212 213 if (ipDomainMatcher.matches()) { 214 InetAddressValidator inetAddressValidator = 215 InetAddressValidator.getInstance(); 216 return inetAddressValidator.isValid(ipDomainMatcher.group(1)); 217 } 218 // Domain is symbolic name 219 if (allowTld) { 220 return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain)); 221 } else { 222 return domainValidator.isValid(domain); 223 } 224 } 225 226 /** 227 * Returns true if the user component of an email address is valid. 228 * 229 * @param user being validated 230 * @return true if the user name is valid. 231 */ 232 protected boolean isValidUser(String user) { 233 234 if (user == null || user.length() > MAX_USERNAME_LEN) { 235 return false; 236 } 237 238 return USER_PATTERN.matcher(user).matches(); 239 } 240 241}