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 * https://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 * Perform email validations. 025 * <p> 026 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a> 027 * https://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 * @since 1.4 034 */ 035public class EmailValidator implements Serializable { 036 037 private static final long serialVersionUID = 1705927040799295880L; 038 039 private static final String SPECIAL_CHARS = "\\p{Cc}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; 040 private static final String VALID_CHARS = "(\\\\[^\\p{Cc}])|[^\\s" + SPECIAL_CHARS + "]"; 041 private static final String QUOTED_USER = "(\"(\\\\\"|[^\"\\p{Cc}])*\")"; 042 private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; 043 044 private static final String EMAIL_REGEX = "^(.+)@(\\S+)$"; 045 046 /** 047 * RFC 5321 section 4.1.3: an IPv6 address literal carries the "IPv6:" tag (case-insensitive), an IPv4 literal is untagged. 048 */ 049 private static final String IP_DOMAIN_REGEX = "^\\[((?i)IPv6:)?(.*)\\]$"; 050 private static final String USER_REGEX = "^" + WORD + "(\\." + WORD + ")*$"; 051 052 private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); 053 private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); 054 private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); 055 056 private static final int MAX_USERNAME_LEN = 64; 057 058 /** 059 * Singleton instance of this class, which 060 * doesn't consider local addresses as valid. 061 */ 062 private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false, false); 063 064 /** 065 * Singleton instance of this class, which 066 * doesn't consider local addresses as valid. 067 */ 068 private static final EmailValidator EMAIL_VALIDATOR_WITH_TLD = new EmailValidator(false, true); 069 070 /** 071 * Singleton instance of this class, which does 072 * consider local addresses valid. 073 */ 074 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true, false); 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 /** 083 * Returns the Singleton instance of this validator. 084 * 085 * @return singleton instance of this validator. 086 */ 087 public static EmailValidator getInstance() { 088 return EMAIL_VALIDATOR; 089 } 090 091 /** 092 * Returns the Singleton instance of this validator, 093 * with local validation as required. 094 * 095 * @param allowLocal Should local addresses be considered valid? 096 * @return singleton instance of this validator 097 */ 098 public static EmailValidator getInstance(final boolean allowLocal) { 099 return getInstance(allowLocal, false); 100 } 101 102 /** 103 * Returns the Singleton instance of this validator, with local validation as required. 104 * 105 * @param allowLocal Should local addresses be considered valid? 106 * @param allowTld Should TLDs be allowed? 107 * @return singleton instance of this validator 108 */ 109 public static EmailValidator getInstance(final boolean allowLocal, final boolean allowTld) { 110 if (allowLocal) { 111 if (allowTld) { 112 return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD; 113 } 114 return EMAIL_VALIDATOR_WITH_LOCAL; 115 } 116 if (allowTld) { 117 return EMAIL_VALIDATOR_WITH_TLD; 118 } 119 return EMAIL_VALIDATOR; 120 } 121 122 /** 123 * Whether to allow TLDs. 124 */ 125 private final boolean allowTld; 126 127 /** 128 * The domain validator. 129 */ 130 private final DomainValidator domainValidator; 131 132 /** 133 * Protected constructor for subclasses to use. 134 * 135 * @param allowLocal Should local addresses be considered valid? 136 */ 137 protected EmailValidator(final boolean allowLocal) { 138 this(allowLocal, false); 139 } 140 141 /** 142 * Protected constructor for subclasses to use. 143 * 144 * @param allowLocal Should local addresses be considered valid? 145 * @param allowTld Should TLDs be allowed? 146 */ 147 protected EmailValidator(final boolean allowLocal, final boolean allowTld) { 148 this.allowTld = allowTld; 149 this.domainValidator = DomainValidator.getInstance(allowLocal); 150 } 151 152 /** 153 * constructor for creating instances with the specified domainValidator 154 * 155 * @param allowLocal Should local addresses be considered valid? 156 * @param allowTld Should TLDs be allowed? 157 * @param domainValidator allow override of the DomainValidator. 158 * The instance must have the same allowLocal setting. 159 * @since 1.7 160 */ 161 public EmailValidator(final boolean allowLocal, final boolean allowTld, final DomainValidator domainValidator) { 162 this.allowTld = allowTld; 163 if (domainValidator == null) { 164 throw new IllegalArgumentException("DomainValidator cannot be null"); 165 } 166 if (domainValidator.isAllowLocal() != allowLocal) { 167 throw new IllegalArgumentException("DomainValidator must agree with allowLocal setting"); 168 } 169 this.domainValidator = domainValidator; 170 } 171 172 /** 173 * Checks if a field has a valid e-mail address. 174 * 175 * @param email The value validation is being performed on. A {@code null} 176 * value is considered invalid. 177 * @return true if the email address is valid. 178 */ 179 public boolean isValid(final String email) { 180 if (email == null || email.endsWith(".")) { // check this first - it's cheap! 181 return false; 182 } 183 // Check the whole email address structure 184 final Matcher emailMatcher = EMAIL_PATTERN.matcher(email); 185 if (!emailMatcher.matches() || !isValidUser(emailMatcher.group(1)) || !isValidDomain(emailMatcher.group(2))) { 186 return false; 187 } 188 return true; 189 } 190 191 /** 192 * Returns true if the domain component of an email address is valid. 193 * 194 * @param domain being validated, may be in IDN format 195 * @return true if the email address's domain is valid. 196 */ 197 protected boolean isValidDomain(final String domain) { 198 // see if domain is an IP address in brackets 199 final Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); 200 201 if (ipDomainMatcher.matches()) { 202 final InetAddressValidator inetAddressValidator = InetAddressValidator.getInstance(); 203 if (ipDomainMatcher.group(1) != null) { 204 return inetAddressValidator.isValidInet6Address(ipDomainMatcher.group(2)); 205 } 206 return inetAddressValidator.isValidInet4Address(ipDomainMatcher.group(2)); 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}