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 * This class is a Singleton; you can retrieve the instance via the getInstance() method. 028 * </p> 029 * <p> 030 * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a> 031 * http://javascript.internet.com 032 * </p> 033 * <p> 034 * This implementation is not guaranteed to catch all possible errors in an email address. 035 * For example, an address like nobody@noplace.somedog will pass validator, even though there 036 * is no TLD "somedog" 037 * </p>. 038 * 039 * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $ 040 * @since Validator 1.4 041 */ 042public class EmailValidator extends AbstractValidator { 043 044 private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; 045 private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]"; 046 private static final String QUOTED_USER = "(\"[^\"]*\")"; 047 private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; 048 049 private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$"; 050 private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$"; 051 private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; 052 private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$"; 053 054 private static final Pattern MATCH_ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX); 055 private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); 056 private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); 057 private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); 058 059 private final boolean allowLocal; 060 061 /** 062 * Singleton instance of this class, which 063 * doesn't consider local addresses as valid. 064 */ 065 private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(false); 066 067 /** 068 * Singleton instance of this class, which does 069 * consider local addresses valid. 070 */ 071 private static final EmailValidator EMAIL_VALIDATOR_WITH_LOCAL = new EmailValidator(true); 072 073 /** 074 * Returns the Singleton instance of this validator. 075 * 076 * @return singleton instance of this validator. 077 */ 078 public static EmailValidator getInstance() { 079 return EMAIL_VALIDATOR; 080 } 081 082 /** 083 * Returns the Singleton instance of this validator, 084 * with local validation as required. 085 * 086 * @param allowLocal Should local addresses be considered valid? 087 * @return singleton instance of this validator 088 */ 089 public static EmailValidator getInstance(boolean allowLocal) { 090 if(allowLocal) { 091 return EMAIL_VALIDATOR_WITH_LOCAL; 092 } 093 return EMAIL_VALIDATOR; 094 } 095 096 /** 097 * Protected constructor for subclasses to use. 098 * 099 * @param allowLocal Should local addresses be considered valid? 100 */ 101 protected EmailValidator(boolean allowLocal) { 102 super(); 103 this.allowLocal = allowLocal; 104 } 105 106 /** 107 * <p>Checks if a field has a valid e-mail address.</p> 108 * 109 * @param email The value validation is being performed on. A <code>null</code> 110 * value is considered invalid. 111 * @return true if the email address is valid. 112 */ 113 @Override 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 setErrorMessage(tr("E-mail address contains non-ascii characters")); 122 setFix(email.replaceAll("[^\\p{ASCII}]+", "")); 123 return false; 124 } 125 126 // Check the whole email address structure 127 Matcher emailMatcher = EMAIL_PATTERN.matcher(email); 128 if (!emailMatcher.matches()) { 129 setErrorMessage(tr("E-mail address is invalid")); 130 return false; 131 } 132 133 if (email.endsWith(".")) { 134 setErrorMessage(tr("E-mail address is invalid")); 135 return false; 136 } 137 138 String username = emailMatcher.group(1); 139 if (!isValidUser(username)) { 140 setErrorMessage(tr("E-mail address contains an invalid username: {0}", username)); 141 return false; 142 } 143 144 String domain = emailMatcher.group(2); 145 if (!isValidDomain(domain)) { 146 setErrorMessage(tr("E-mail address contains an invalid domain: {0}", domain)); 147 return false; 148 } 149 150 return true; 151 } 152 153 /** 154 * Returns true if the domain component of an email address is valid. 155 * 156 * @param domain being validated. 157 * @return true if the email address's domain is valid. 158 */ 159 protected boolean isValidDomain(String domain) { 160 // see if domain is an IP address in brackets 161 Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); 162 163 if (ipDomainMatcher.matches()) { 164 InetAddressValidator inetAddressValidator = 165 InetAddressValidator.getInstance(); 166 return inetAddressValidator.isValid(ipDomainMatcher.group(1)); 167 } else { 168 // Domain is symbolic name 169 DomainValidator domainValidator = 170 DomainValidator.getInstance(allowLocal); 171 return domainValidator.isValid(domain); 172 } 173 } 174 175 /** 176 * Returns true if the user component of an email address is valid. 177 * 178 * @param user being validated 179 * @return true if the user name is valid. 180 */ 181 protected boolean isValidUser(String user) { 182 return USER_PATTERN.matcher(user).matches(); 183 } 184 185}