001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.validation;
018:
019: import java.util.regex.Pattern;
020: import java.util.regex.Matcher;
021: import java.util.List;
022: import java.net.Socket;
023: import java.net.UnknownHostException;
024: import java.io.IOException;
025: import java.io.PrintStream;
026: import java.io.BufferedReader;
027: import java.io.InputStreamReader;
028:
029: import velosurf.util.Logger;
030: import velosurf.util.DNSResolver;
031:
032: /**
033: * <p>An 'email' constraint. Syntax is:</p>
034: *
035: * <pre>
036: * <<i>column</i> type="email"/>
037: * </pre>
038: * <p>Or:</p>
039: * <pre>
040: * <<i>column</i>>
041: * <email [dns-check="true | <b>false</b>"] [smtp-check="true | <b>false</b>" ] [message="<i>error-message</i>"]>
042: * </<i>column</i>>
043: * </pre>
044: *
045: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
046: */
047: public class Email extends FieldConstraint {
048: /** whether to check dns. */
049: private boolean dnsCheck = false;
050: /** whether to check smtp server. */
051: private boolean smtpCheck = false;
052: /** valid email pattern. */
053: private static Pattern validEmail = null;
054:
055: static {
056: /* Do we really want to allow all those strange characters in emails?
057: Well, that's what the RFC2822 seems to allow... */
058: String atom = "[a-zA-Z0-9!#$%&'*+-/=?^_`{|}~]";
059: String domain = "(?:[a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9]+)?)";
060: validEmail = Pattern.compile("(^" + atom + "+" + "(?:\\."
061: + atom + ")*)" + "@((?:" + domain + "{1,63}\\.)+"
062: + domain + "{2,63})$", Pattern.CASE_INSENSITIVE);
063: }
064:
065: /**
066: * Default constructor.
067: */
068: public Email() {
069: this (false, false);
070: }
071:
072: /**
073: * Constructor.
074: * @param dnsCheck whether to validate this email using a DNS query
075: * @param smtpCheck whether to validate this email using an STMP query
076: */
077: public Email(boolean dnsCheck, boolean smtpCheck) {
078: this .dnsCheck = dnsCheck;
079: this .smtpCheck = smtpCheck;
080: setMessage("field {0}: '{1}' is not a valid email");
081: }
082:
083: /**
084: * Validate data against this constraint.
085: * @param data data to validate
086: * @return whether data is valid
087: */
088: public boolean validate(Object data) {
089: if (data == null || data.toString().length() == 0)
090: return true;
091: Matcher matcher = validEmail.matcher(data.toString());
092: if (!matcher.matches()) {
093: return false;
094: }
095: String user = matcher.group(1);
096: String hostname = matcher.group(2);
097: /* first, DNS validation */
098: if (dnsCheck && !DNSResolver.checkDNS(hostname, true)) {
099: return false;
100: }
101: /* then, SMTP */
102: if (smtpCheck && !checkSMTP(user, hostname)) {
103: return false;
104: }
105: return true;
106: }
107:
108: /**
109: * Check SMTP server.
110: * @param user username
111: * @param hostname hostname
112: * @return true if valid
113: */
114: private boolean checkSMTP(String user, String hostname) {
115: String response;
116: Socket sock = null;
117: Logger.trace("email validation: checking SMTP for <" + user
118: + "@" + hostname + ">");
119: List<String> mxs = DNSResolver.resolveDNS(hostname, true);
120: if (mxs == null || mxs.size() == 0) {
121: return false;
122: }
123: for (String mx : mxs) {
124: try {
125: Logger
126: .trace("email validation: checking SMTP: trying with MX server "
127: + mx);
128: sock = new FastTimeoutConnect(mx, 25, 3000).connect();
129: if (sock == null) {
130: Logger
131: .trace("email validation: checking SMTP: timeout");
132: continue;
133: }
134: BufferedReader is = new BufferedReader(
135: new InputStreamReader(sock.getInputStream()));
136: PrintStream os = new PrintStream(sock.getOutputStream());
137: response = is.readLine();
138: Logger.trace(" " + response);
139: if (!response.startsWith("220 ")) {
140: Logger
141: .trace("email validation: checking SMTP: failed after connection");
142: if (response.startsWith("4")) {
143: /* server has problems */
144: continue;
145: } else {
146: return false;
147: }
148: }
149: ;
150: os.println("HELO email-checker@localhost.foo");
151: response = is.readLine();
152: Logger.trace(" " + response);
153: if (!response.startsWith("250 ")) {
154: Logger
155: .trace("email validation: checking SMTP: failed after HELO");
156: if (response.startsWith("4")) {
157: /* server has problems */
158: continue;
159: } else {
160: return false;
161: }
162: }
163: ;
164: /* note that if the mail server issues a premature DNS check, the process may fail
165: for valid emails */
166: os.println("MAIL FROM:<email-checker@localhost.foo>");
167: response = is.readLine();
168: Logger.trace(" " + response);
169: if (!response.startsWith("250 ")) {
170: Logger
171: .trace("email validation: checking SMTP: failed after MAIL FROM");
172: if (response.startsWith("4")) {
173: /* server has problems */
174: continue;
175: } else {
176: return false;
177: }
178: }
179: ;
180: os.println("RCPT TO:<" + user + "@" + hostname + ">");
181: response = is.readLine();
182: Logger.trace(" " + response);
183: if (!response.startsWith("250 ")) {
184: Logger
185: .trace("email validation: checking SMTP: failed after RCPT TO");
186: if (response.startsWith("4")) {
187: /* server has problems */
188: continue;
189: } else {
190: return false;
191: }
192: }
193: ;
194: try {
195: os.println("QUIT");
196: } catch (Exception e) {
197: }
198: Logger
199: .trace("email validation: checking SMTP: success");
200: return true;
201: } catch (Exception e) {
202: Logger
203: .trace("email validation: checking SMTP: failure with exception: "
204: + e.getMessage());
205: } finally {
206: if (sock != null && !sock.isClosed()) {
207: try {
208: sock.close();
209: } catch (IOException ioe) {
210: }
211: }
212: }
213: }
214: Logger
215: .trace("email validation: checking SMTP: failure for all MXs");
216: return false;
217: }
218:
219: /**
220: * A socket with short timeout.
221: */
222: class FastTimeoutConnect implements Runnable {
223: /** host. */
224: private String host;
225: /** port. */
226: private int port;
227: /** connection successfull? */
228: private boolean done = false;
229: /** timeout. */
230: private int timeout;
231: /** wrapped socket. */
232: private Socket socket = null;
233: /** thrown I/O exception. */
234: private IOException ioe;
235: /** throws unknown host exception. */
236: private UnknownHostException uhe;
237:
238: /**
239: * Constructor.
240: * @param h host
241: * @param p port
242: * @param t timeout
243: */
244: public FastTimeoutConnect(String h, int p, int t) {
245: host = h;
246: port = p;
247: timeout = t;
248: }
249:
250: /**
251: * Connect.
252: * @return socket
253: * @throws IOException
254: * @throws UnknownHostException
255: */
256: public Socket connect() throws IOException,
257: UnknownHostException {
258: int waited = 0;
259: Thread thread = new Thread(this );
260: thread.start();
261: while (!done && waited < timeout) {
262: try {
263: Thread.sleep(100);
264: waited += 100;
265: } catch (InterruptedException e) {
266: throw new IOException("sleep interrupted");
267: }
268: }
269: if (!done)
270: thread.interrupt();
271: if (ioe != null)
272: throw ioe;
273: if (uhe != null)
274: throw uhe;
275: return socket;
276: }
277:
278: /**
279: * connection process.
280: */
281: public void run() {
282: try {
283: socket = new Socket(host, port);
284: } catch (UnknownHostException uhe) {
285: this .uhe = uhe;
286: } catch (IOException ioe) {
287: this .ioe = ioe;
288: } finally {
289: done = true;
290: }
291: }
292: }
293:
294: /**
295: * return a string representation for this constraint.
296: * @return string
297: */
298: public String toString() {
299: return "type email, check-dns=" + dnsCheck + ", check-smtp="
300: + smtpCheck;
301:
302: }
303:
304: }
|