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.openstreetmap.josm.data.validation.routines; 018 019import static org.openstreetmap.josm.tools.I18n.tr; 020 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023 024/** 025 * <p>Perform email validations.</p> 026 * <p> 027 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a> 028 * http://javascript.internet.com 029 * </p> 030 * <p> 031 * This implementation is not guaranteed to catch all possible errors in an email address. 032 * </p>. 033 * 034 * @version $Revision: 1723573 $ 035 * @since Validator 1.4 036 */ 037public class EmailValidator extends AbstractValidator { 038 039 private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; 040 private static final String VALID_CHARS = "(\\\\.)|[^\\s" + SPECIAL_CHARS + "]"; 041 private static final String QUOTED_USER = "(\"(\\\\\"|[^\"])*\")"; 042 private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; 043 044 private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$"; 045 private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; 046 private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$"; 047 048 private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); 049 private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); 050 private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); 051 052 private static final int MAX_USERNAME_LEN = 64; 053 054 private final boolean allowLocal; 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 /** 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 * @param allowTld Should TLDs be allowed? 097 * @return singleton instance of this validator 098 */ 099 public static EmailValidator getInstance(boolean allowLocal, boolean allowTld) { 100 if (allowLocal) { 101 if (allowTld) { 102 return EMAIL_VALIDATOR_WITH_LOCAL_WITH_TLD; 103 } else { 104 return EMAIL_VALIDATOR_WITH_LOCAL; 105 } 106 } else { 107 if (allowTld) { 108 return EMAIL_VALIDATOR_WITH_TLD; 109 } else { 110 return EMAIL_VALIDATOR; 111 } 112 } 113 } 114 115 /** 116 * Returns the Singleton instance of this validator, 117 * with local validation as required. 118 * 119 * @param allowLocal Should local addresses be considered valid? 120 * @return singleton instance of this validator 121 */ 122 public static EmailValidator getInstance(boolean allowLocal) { 123 return getInstance(allowLocal, false); 124 } 125 126 /** 127 * Protected constructor for subclasses to use. 128 * 129 * @param allowLocal Should local addresses be considered valid? 130 * @param allowTld Should TLDs be allowed? 131 */ 132 protected EmailValidator(boolean allowLocal, boolean allowTld) { 133 super(); 134 this.allowLocal = allowLocal; 135 this.allowTld = allowTld; 136 } 137 138 /** 139 * Protected constructor for subclasses to use. 140 * 141 * @param allowLocal Should local addresses be considered valid? 142 */ 143 protected EmailValidator(boolean allowLocal) { 144 super(); 145 this.allowLocal = allowLocal; 146 this.allowTld = false; 147 } 148 149 /** 150 * <p>Checks if a field has a valid e-mail address.</p> 151 * 152 * @param email The value validation is being performed on. A <code>null</code> 153 * value is considered invalid. 154 * @return true if the email address is valid. 155 */ 156 @Override 157 public boolean isValid(String email) { 158 if (email == null) { 159 return false; 160 } 161 162 if (email.endsWith(".")) { // check this first - it's cheap! 163 setErrorMessage(tr("E-mail address is invalid")); 164 return false; 165 } 166 167 // Check the whole email address structure 168 Matcher emailMatcher = EMAIL_PATTERN.matcher(email); 169 if (!emailMatcher.matches()) { 170 setErrorMessage(tr("E-mail address is invalid")); 171 return false; 172 } 173 174 String username = emailMatcher.group(1); 175 if (!isValidUser(username)) { 176 setErrorMessage(tr("E-mail address contains an invalid username: {0}", username)); 177 return false; 178 } 179 180 String domain = emailMatcher.group(2); 181 if (!isValidDomain(domain)) { 182 setErrorMessage(tr("E-mail address contains an invalid domain: {0}", domain)); 183 return false; 184 } 185 186 return true; 187 } 188 189 @Override 190 public String getValidatorName() { 191 return tr("Email validator"); 192 } 193 194 /** 195 * Returns true if the domain component of an email address is valid. 196 * 197 * @param domain being validated, may be in IDN format 198 * @return true if the email address's domain is valid. 199 */ 200 protected boolean isValidDomain(String domain) { 201 // see if domain is an IP address in brackets 202 Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); 203 204 if (ipDomainMatcher.matches()) { 205 InetAddressValidator inetAddressValidator = 206 InetAddressValidator.getInstance(); 207 return inetAddressValidator.isValid(ipDomainMatcher.group(1)); 208 } 209 // Domain is symbolic name 210 DomainValidator domainValidator = 211 DomainValidator.getInstance(allowLocal); 212 if (allowTld) { 213 return domainValidator.isValid(domain) || (!domain.startsWith(".") && domainValidator.isValidTld(domain)); 214 } else { 215 return domainValidator.isValid(domain); 216 } 217 } 218 219 /** 220 * Returns true if the user component of an email address is valid. 221 * 222 * @param user being validated 223 * @return true if the user name is valid. 224 */ 225 protected boolean isValidUser(String user) { 226 227 if (user == null || user.length() > MAX_USERNAME_LEN) { 228 return false; 229 } 230 231 return USER_PATTERN.matcher(user).matches(); 232 } 233 234}