View Javadoc

1   /*
2    * Copyright 2004-2010 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  
17  package org.seasar.cubby.validator.validators;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  import org.seasar.cubby.action.MessageInfo;
25  import org.seasar.cubby.internal.util.StringUtils;
26  import org.seasar.cubby.validator.ScalarFieldValidator;
27  import org.seasar.cubby.validator.ValidationContext;
28  
29  /**
30   * <a href="http://www.ietf.org/rfc/rfc2821.txt">RFC2821</a>
31   * に準拠したメールアドレスかを検証します。
32   * <p>
33   * <table>
34   * <caption>検証エラー時に設定するエラーメッセージ</caption> <tbody>
35   * <tr>
36   * <th scope="row">デフォルトのキー</th>
37   * <td>valid.email</td>
38   * </tr>
39   * <tr>
40   * <th scope="row">置換文字列</th>
41   * <td>
42   * <ol start="0">
43   * <li>フィールド名</li>
44   * </ol></td>
45   * </tr>
46   * </tbody>
47   * </table>
48   * </p>
49   * 
50   * @see <a href="http://www.ietf.org/rfc/rfc2821.txt">RFC2821</a>
51   * @author agata
52   * @author baba
53   */
54  public class EmailValidator implements ScalarFieldValidator {
55  
56  	private static final String SPECIAL_CHARS = "\\(\\)<>@,;:\\\\\\\"\\.\\[\\]";
57  	private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]";
58  	private static final String QUOTED_USER = "(\"[^\"]*\")";
59  	private static final String ATOM = VALID_CHARS + '+';
60  	private static final String WORD = "(" + ATOM + "|" + QUOTED_USER + ")";
61  
62  	private static final String LEGAL_ASCII_PATTERN = "^[\\x00-\\x7F]+$";
63  	private static final String EMAIL_PATTERN = "^(.+)@(.+)$";
64  	private static final String IP_DOMAIN_PATTERN = "^(\\d{1,3})[.](\\d{1,3})[.](\\d{1,3})[.](\\d{1,3})$";
65  
66  	private static final String USER_PATTERN = "^" + WORD + "(\\." + WORD
67  			+ ")*$";
68  	private static final String DOMAIN_PATTERN = "^" + ATOM + "(\\." + ATOM
69  			+ ")*$";
70  	private static final String ATOM_PATTERN = "(" + ATOM + ")";
71  
72  	/**
73  	 * メッセージキー。
74  	 */
75  	private final String messageKey;
76  
77  	/**
78  	 * コンストラクタ
79  	 */
80  	public EmailValidator() {
81  		this("valid.email");
82  	}
83  
84  	/**
85  	 * メッセージキーを指定するコンストラクタ
86  	 * 
87  	 * @param messageKey
88  	 */
89  	public EmailValidator(final String messageKey) {
90  		this.messageKey = messageKey;
91  	}
92  
93  	/**
94  	 * {@inheritDoc}
95  	 */
96  	public void validate(final ValidationContext context, final Object value) {
97  		if (value == null) {
98  			return;
99  		}
100 		if (value instanceof String) {
101 			final String email = (String) value;
102 			if (StringUtils.isEmpty(email)) {
103 				return;
104 			}
105 
106 			boolean match = !email.endsWith(".");
107 			if (match) {
108 				final Pattern pattern = Pattern.compile(LEGAL_ASCII_PATTERN);
109 				final Matcher matchAsciiPat = pattern.matcher(email);
110 				match = matchAsciiPat.matches();
111 			}
112 
113 			if (match) {
114 				final Pattern pattern = Pattern.compile(EMAIL_PATTERN);
115 				final Matcher matcher = pattern.matcher(email);
116 				match = matcher.find();
117 				if (match) {
118 					if (isValidUser(matcher.group(1))
119 							&& isValidDomain(matcher.group(2))) {
120 						return;
121 					}
122 				}
123 			}
124 		}
125 
126 		final MessageInfo messageInfo = new MessageInfo();
127 		messageInfo.setKey(this.messageKey);
128 		context.addMessageInfo(messageInfo);
129 	}
130 
131 	private boolean isValidDomain(final String domain) {
132 		Pattern pattern = Pattern.compile(IP_DOMAIN_PATTERN);
133 		final Matcher ipAddressMatcher = pattern.matcher(domain);
134 
135 		if (ipAddressMatcher.find()) {
136 			if (isValidIpAddress(ipAddressMatcher)) {
137 				return true;
138 			}
139 		} else {
140 			pattern = Pattern.compile(DOMAIN_PATTERN);
141 			final Matcher domainMatcher = pattern.matcher(domain);
142 			if (domainMatcher.matches()) {
143 				if (isValidSymbolicDomain(domain)) {
144 					return true;
145 				}
146 			}
147 		}
148 		return false;
149 	}
150 
151 	private boolean isValidUser(final String user) {
152 		final Pattern pattern = Pattern.compile(USER_PATTERN);
153 		final Matcher userMatcher = pattern.matcher(user);
154 		return userMatcher.matches();
155 	}
156 
157 	private boolean isValidIpAddress(final Matcher ipAddressMatcher) {
158 		for (int i = 1; i <= 4; i++) {
159 			final String ipSegment = ipAddressMatcher.group(i);
160 			if (ipSegment == null || ipSegment.length() <= 0) {
161 				return false;
162 			}
163 
164 			int iIpSegment = 0;
165 
166 			try {
167 				iIpSegment = Integer.parseInt(ipSegment);
168 			} catch (final NumberFormatException e) {
169 				return false;
170 			}
171 
172 			if (iIpSegment > 255) {
173 				return false;
174 			}
175 
176 		}
177 		return true;
178 	}
179 
180 	private boolean isValidSymbolicDomain(final String domain) {
181 		final List<String> domainSegments = new ArrayList<String>();
182 		boolean match = true;
183 
184 		final Pattern pattern = Pattern.compile(ATOM_PATTERN);
185 		String domainSegment;
186 		String currentDomain = domain;
187 		while (match) {
188 			final Matcher atomMatcher = pattern.matcher(currentDomain);
189 			match = atomMatcher.find();
190 			if (match) {
191 				domainSegment = atomMatcher.group(1);
192 				domainSegments.add(domainSegment);
193 				final int domainSegmentLength = domainSegment.length() + 1;
194 				currentDomain = domainSegmentLength >= currentDomain.length() ? ""
195 						: currentDomain.substring(domainSegmentLength);
196 			}
197 		}
198 
199 		final int size = domainSegments.size();
200 		if (size > 0) {
201 			final String end = domainSegments.get(size - 1);
202 			if (end.length() < 2 || end.length() > 4) {
203 				return false;
204 			}
205 		}
206 
207 		if (size < 2) {
208 			return false;
209 		}
210 
211 		return true;
212 	}
213 
214 }