View Javadoc

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