KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > mailet > MailAddress


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 package org.apache.mailet;
19
20 import java.util.Locale JavaDoc;
21 import javax.mail.internet.InternetAddress JavaDoc;
22 import javax.mail.internet.ParseException JavaDoc;
23
24 /**
25  * A representation of an email address.
26  * <p>This class encapsulates functionalities to access to different
27  * parts of an email address without dealing with its parsing.</p>
28  *
29  * <p>A MailAddress is an address specified in the MAIL FROM and
30  * RCPT TO commands in SMTP sessions. These are either passed by
31  * an external server to the mailet-compliant SMTP server, or they
32  * are created programmatically by the mailet-compliant server to
33  * send to another (external) SMTP server. Mailets and matchers
34  * use the MailAddress for the purpose of evaluating the sender
35  * and recipient(s) of a message.</p>
36  *
37  * <p>MailAddress parses an email address as defined in RFC 821
38  * (SMTP) p. 30 and 31 where addresses are defined in BNF convention.
39  * As the mailet API does not support the aged "SMTP-relayed mail"
40  * addressing protocol, this leaves all addresses to be a <mailbox>,
41  * as per the spec. The MailAddress's "user" is the <local-part> of
42  * the <mailbox> and "host" is the <domain> of the mailbox.</p>
43  *
44  * <p>This class is a good way to validate email addresses as there are
45  * some valid addresses which would fail with a simpler approach
46  * to parsing address. It also removes parsing burden from
47  * mailets and matchers that might not realize the flexibility of an
48  * SMTP address. For instance, "serge@home"@lokitech.com is a valid
49  * SMTP address (the quoted text serge@home is the user and
50  * lokitech.com is the host). This means all current parsing to date
51  * is incorrect as we just find the first @ and use that to separate
52  * user from host.</p>
53  *
54  * <p>This parses an address as per the BNF specification for <mailbox>
55  * from RFC 821 on page 30 and 31, section 4.1.2. COMMAND SYNTAX.
56  * http://www.freesoft.org/CIE/RFC/821/15.htm</p>
57  *
58  * @version 1.0
59  */

60 public class MailAddress implements java.io.Serializable JavaDoc {
61     //We hardcode the serialVersionUID so that from James 1.2 on,
62
// MailAddress will be deserializable (so your mail doesn't get lost)
63
public static final long serialVersionUID = 2779163542539434916L;
64
65     private final static char[] SPECIAL =
66     {'<', '>', '(', ')', '[', ']', '\\', '.', ',', ';', ':', '@', '\"'};
67
68     private String JavaDoc user = null;
69     private String JavaDoc host = null;
70     //Used for parsing
71
private int pos = 0;
72
73     /**
74      * <p>Construct a MailAddress parsing the provided <code>String</code> object.</p>
75      *
76      * <p>The <code>personal</code> variable is left empty.</p>
77      *
78      * @param address the email address compliant to the RFC822 format
79      * @throws ParseException if the parse failed
80      */

81     public MailAddress(String JavaDoc address) throws ParseException JavaDoc {
82         address = address.trim();
83         StringBuffer JavaDoc userSB = new StringBuffer JavaDoc();
84         StringBuffer JavaDoc hostSB = new StringBuffer JavaDoc();
85         //Begin parsing
86
//<mailbox> ::= <local-part> "@" <domain>
87

88         try {
89             //parse local-part
90
//<local-part> ::= <dot-string> | <quoted-string>
91
if (address.charAt(pos) == '\"') {
92                 userSB.append(parseQuotedLocalPart(address));
93             } else {
94                 userSB.append(parseUnquotedLocalPart(address));
95             }
96             if (userSB.toString().length() == 0) {
97                 throw new ParseException JavaDoc("No local-part (user account) found at position " + (pos + 1));
98             }
99
100             //find @
101
if (pos >= address.length() || address.charAt(pos) != '@') {
102                 throw new ParseException JavaDoc("Did not find @ between local-part and domain at position " + (pos + 1));
103             }
104             pos++;
105
106             //parse domain
107
//<domain> ::= <element> | <element> "." <domain>
108
//<element> ::= <name> | "#" <number> | "[" <dotnum> "]"
109
while (true) {
110                 if (address.charAt(pos) == '#') {
111                     hostSB.append(parseNumber(address));
112                 } else if (address.charAt(pos) == '[') {
113                     hostSB.append(parseDotNum(address));
114                 } else {
115                     hostSB.append(parseDomainName(address));
116                 }
117                 if (pos >= address.length()) {
118                     break;
119                 }
120                 if (address.charAt(pos) == '.') {
121                     hostSB.append('.');
122                     pos++;
123                     continue;
124                 }
125                 break;
126             }
127
128             if (hostSB.toString().length() == 0) {
129                 throw new ParseException JavaDoc("No domain found at position " + (pos + 1));
130             }
131         } catch (IndexOutOfBoundsException JavaDoc ioobe) {
132             throw new ParseException JavaDoc("Out of data at position " + (pos + 1));
133         }
134
135         user = userSB.toString();
136         host = hostSB.toString();
137     }
138
139     /**
140      * Construct a MailAddress with the provided personal name and email
141      * address.
142      *
143      * @param user the username or account name on the mail server
144      * @param host the server that should accept messages for this user
145      * @throws ParseException if the parse failed
146      */

147     public MailAddress(String JavaDoc newUser, String JavaDoc newHost) throws ParseException JavaDoc {
148         /* NEEDS TO BE REWORKED TO VALIDATE EACH CHAR */
149         user = newUser;
150         host = newHost;
151     }
152
153     /**
154      * Constructs a MailAddress from a JavaMail InternetAddress, using only the
155      * email address portion, discarding the personal name.
156      */

157     public MailAddress(InternetAddress JavaDoc address) throws ParseException JavaDoc {
158         this(address.getAddress());
159     }
160
161     /**
162      * Return the host part.
163      *
164      * @return a <code>String</code> object representing the host part
165      * of this email address. If the host is of the dotNum form
166      * (e.g. [yyy.yyy.yyy.yyy]) then strip the braces first.
167      */

168     public String JavaDoc getHost() {
169         if (!(host.startsWith("[") && host.endsWith("]"))) {
170             return host;
171         } else {
172             return host.substring(1, host.length() -1);
173         }
174     }
175
176     /**
177      * Return the user part.
178      *
179      * @return a <code>String</code> object representing the user part
180      * of this email address.
181      * @throws AddressException if the parse failed
182      */

183     public String JavaDoc getUser() {
184         return user;
185     }
186
187     public String JavaDoc toString() {
188         StringBuffer JavaDoc addressBuffer =
189             new StringBuffer JavaDoc(128)
190                     .append(user)
191                     .append("@")
192                     .append(host);
193         return addressBuffer.toString();
194     }
195
196     public InternetAddress JavaDoc toInternetAddress() {
197         try {
198             return new InternetAddress JavaDoc(toString());
199         } catch (javax.mail.internet.AddressException JavaDoc ae) {
200             //impossible really
201
return null;
202         }
203     }
204
205     public boolean equals(Object JavaDoc obj) {
206         if (obj == null) {
207             return false;
208         } else if (obj instanceof String JavaDoc) {
209             String JavaDoc theString = (String JavaDoc)obj;
210             return toString().equalsIgnoreCase(theString);
211         } else if (obj instanceof MailAddress) {
212             MailAddress addr = (MailAddress)obj;
213             return getUser().equalsIgnoreCase(addr.getUser()) && getHost().equalsIgnoreCase(addr.getHost());
214         }
215         return false;
216     }
217
218     /**
219      * Return a hashCode for this object which should be identical for addresses
220      * which are equivalent. This is implemented by obtaining the default
221      * hashcode of the String representation of the MailAddress. Without this
222      * explicit definition, the default hashCode will create different hashcodes
223      * for separate object instances.
224      *
225      * @return the hashcode.
226      */

227     public int hashCode() {
228         return toString().toLowerCase(Locale.US).hashCode();
229     }
230
231     private String JavaDoc parseQuotedLocalPart(String JavaDoc address) throws ParseException JavaDoc {
232         StringBuffer JavaDoc resultSB = new StringBuffer JavaDoc();
233         resultSB.append('\"');
234         pos++;
235         //<quoted-string> ::= """ <qtext> """
236
//<qtext> ::= "\" <x> | "\" <x> <qtext> | <q> | <q> <qtext>
237
while (true) {
238             if (address.charAt(pos) == '\"') {
239                 resultSB.append('\"');
240                 //end of quoted string... move forward
241
pos++;
242                 break;
243             }
244             if (address.charAt(pos) == '\\') {
245                 resultSB.append('\\');
246                 pos++;
247                 //<x> ::= any one of the 128 ASCII characters (no exceptions)
248
char x = address.charAt(pos);
249                 if (x < 0 || x > 127) {
250                     throw new ParseException JavaDoc("Invalid \\ syntaxed character at position " + (pos + 1));
251                 }
252                 resultSB.append(x);
253                 pos++;
254             } else {
255                 //<q> ::= any one of the 128 ASCII characters except <CR>,
256
//<LF>, quote ("), or backslash (\)
257
char q = address.charAt(pos);
258                 if (q <= 0 || q == '\n' || q == '\r' || q == '\"' || q == '\\') {
259                     throw new ParseException JavaDoc("Unquoted local-part (user account) must be one of the 128 ASCI characters exception <CR>, <LF>, quote (\"), or backslash (\\) at position " + (pos + 1));
260                 }
261                 resultSB.append(q);
262                 pos++;
263             }
264         }
265         return resultSB.toString();
266     }
267
268     private String JavaDoc parseUnquotedLocalPart(String JavaDoc address) throws ParseException JavaDoc {
269         StringBuffer JavaDoc resultSB = new StringBuffer JavaDoc();
270         //<dot-string> ::= <string> | <string> "." <dot-string>
271
boolean lastCharDot = false;
272         while (true) {
273             //<string> ::= <char> | <char> <string>
274
//<char> ::= <c> | "\" <x>
275
if (address.charAt(pos) == '\\') {
276                 resultSB.append('\\');
277                 pos++;
278                 //<x> ::= any one of the 128 ASCII characters (no exceptions)
279
char x = address.charAt(pos);
280                 if (x < 0 || x > 127) {
281                     throw new ParseException JavaDoc("Invalid \\ syntaxed character at position " + (pos + 1));
282                 }
283                 resultSB.append(x);
284                 pos++;
285                 lastCharDot = false;
286             } else if (address.charAt(pos) == '.') {
287                 resultSB.append('.');
288                 pos++;
289                 lastCharDot = true;
290             } else if (address.charAt(pos) == '@') {
291                 //End of local-part
292
break;
293             } else {
294                 //<c> ::= any one of the 128 ASCII characters, but not any
295
// <special> or <SP>
296
//<special> ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "."
297
// | "," | ";" | ":" | "@" """ | the control
298
// characters (ASCII codes 0 through 31 inclusive and
299
// 127)
300
//<SP> ::= the space character (ASCII code 32)
301
char c = address.charAt(pos);
302                 if (c <= 31 || c >= 127 || c == ' ') {
303                     throw new ParseException JavaDoc("Invalid character in local-part (user account) at position " + (pos + 1));
304                 }
305                 for (int i = 0; i < SPECIAL.length; i++) {
306                     if (c == SPECIAL[i]) {
307                         throw new ParseException JavaDoc("Invalid character in local-part (user account) at position " + (pos + 1));
308                     }
309                 }
310                 resultSB.append(c);
311                 pos++;
312                 lastCharDot = false;
313             }
314         }
315         if (lastCharDot) {
316             throw new ParseException JavaDoc("local-part (user account) ended with a \".\", which is invalid.");
317         }
318         return resultSB.toString();
319     }
320
321     private String JavaDoc parseNumber(String JavaDoc address) throws ParseException JavaDoc {
322         //<number> ::= <d> | <d> <number>
323

324         StringBuffer JavaDoc resultSB = new StringBuffer JavaDoc();
325         //We keep the position from the class level pos field
326
while (true) {
327             if (pos >= address.length()) {
328                 break;
329             }
330             //<d> ::= any one of the ten digits 0 through 9
331
char d = address.charAt(pos);
332             if (d == '.') {
333                 break;
334             }
335             if (d < '0' || d > '9') {
336                 throw new ParseException JavaDoc("In domain, did not find a number in # address at position " + (pos + 1));
337             }
338             resultSB.append(d);
339             pos++;
340         }
341         return resultSB.toString();
342     }
343
344     private String JavaDoc parseDotNum(String JavaDoc address) throws ParseException JavaDoc {
345         //throw away all irrelevant '\' they're not necessary for escaping of '.' or digits, and are illegal as part of the domain-literal
346
while(address.indexOf("\\")>-1){
347              address= address.substring(0,address.indexOf("\\")) + address.substring(address.indexOf("\\")+1);
348         }
349         StringBuffer JavaDoc resultSB = new StringBuffer JavaDoc();
350         //we were passed the string with pos pointing the the [ char.
351
// take the first char ([), put it in the result buffer and increment pos
352
resultSB.append(address.charAt(pos));
353         pos++;
354
355         //<dotnum> ::= <snum> "." <snum> "." <snum> "." <snum>
356
for (int octet = 0; octet < 4; octet++) {
357             //<snum> ::= one, two, or three digits representing a decimal
358
// integer value in the range 0 through 255
359
//<d> ::= any one of the ten digits 0 through 9
360
StringBuffer JavaDoc snumSB = new StringBuffer JavaDoc();
361             for (int digits = 0; digits < 3; digits++) {
362                 char d = address.charAt(pos);
363                 if (d == '.') {
364                     break;
365                 }
366                 if (d == ']') {
367                     break;
368                 }
369                 if (d < '0' || d > '9') {
370                     throw new ParseException JavaDoc("Invalid number at position " + (pos + 1));
371                 }
372                 snumSB.append(d);
373                 pos++;
374             }
375             if (snumSB.toString().length() == 0) {
376                 throw new ParseException JavaDoc("Number not found at position " + (pos + 1));
377             }
378             try {
379                 int snum = Integer.parseInt(snumSB.toString());
380                 if (snum > 255) {
381                     throw new ParseException JavaDoc("Invalid number at position " + (pos + 1));
382                 }
383             } catch (NumberFormatException JavaDoc nfe) {
384                 throw new ParseException JavaDoc("Invalid number at position " + (pos + 1));
385             }
386             resultSB.append(snumSB.toString());
387             if (address.charAt(pos) == ']') {
388                 if (octet < 3) {
389                     throw new ParseException JavaDoc("End of number reached too quickly at " + (pos + 1));
390                 } else {
391                     break;
392                 }
393             }
394             if (address.charAt(pos) == '.') {
395                 resultSB.append('.');
396                 pos++;
397             }
398         }
399         if (address.charAt(pos) != ']') {
400             throw new ParseException JavaDoc("Did not find closing bracket \"]\" in domain at position " + (pos + 1));
401         }
402         resultSB.append(']');
403         pos++;
404         return resultSB.toString();
405     }
406
407     private String JavaDoc parseDomainName(String JavaDoc address) throws ParseException JavaDoc {
408         StringBuffer JavaDoc resultSB = new StringBuffer JavaDoc();
409         //<name> ::= <a> <ldh-str> <let-dig>
410
//<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
411
//<let-dig> ::= <a> | <d>
412
//<let-dig-hyp> ::= <a> | <d> | "-"
413
//<a> ::= any one of the 52 alphabetic characters A through Z
414
// in upper case and a through z in lower case
415
//<d> ::= any one of the ten digits 0 through 9
416

417         // basically, this is a series of letters, digits, and hyphens,
418
// but it can't start with a digit or hypthen
419
// and can't end with a hyphen
420

421         // in practice though, we should relax this as domain names can start
422
// with digits as well as letters. So only check that doesn't start
423
// or end with hyphen.
424
while (true) {
425             if (pos >= address.length()) {
426                 break;
427             }
428             char ch = address.charAt(pos);
429             if ((ch >= '0' && ch <= '9') ||
430                 (ch >= 'a' && ch <= 'z') ||
431                 (ch >= 'A' && ch <= 'Z') ||
432                 (ch == '-')) {
433                 resultSB.append(ch);
434                 pos++;
435                 continue;
436             }
437             if (ch == '.') {
438                 break;
439             }
440             throw new ParseException JavaDoc("Invalid character at " + pos);
441         }
442         String JavaDoc result = resultSB.toString();
443         if (result.startsWith("-") || result.endsWith("-")) {
444             throw new ParseException JavaDoc("Domain name cannot begin or end with a hyphen \"-\" at position " + (pos + 1));
445         }
446         return result;
447     }
448 }
449
Popular Tags