1 /**
2 *
3 * Copyright 2003-2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You 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 implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package javax.mail.internet;
18
19 import java.io.UnsupportedEncodingException;
20 import java.lang.reflect.Array;
21 import java.net.InetAddress;
22 import java.net.UnknownHostException;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.StringTokenizer;
26
27 import javax.mail.Address;
28 import javax.mail.Session;
29
30 import org.apache.geronimo.mail.util.SessionUtil;
31
32 /**
33 * A representation of an Internet email address as specified by RFC822 in
34 * conjunction with a human-readable personal name that can be encoded as
35 * specified by RFC2047.
36 * A typical address is "user@host.domain" and personal name "Joe User"
37 *
38 * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
39 */
40 public class InternetAddress extends Address implements Cloneable {
41 /**
42 * The address in RFC822 format.
43 */
44 protected String address;
45
46 /**
47 * The personal name in RFC2047 format.
48 * Subclasses must ensure that this field is updated if the personal field
49 * is updated; alternatively, it can be invalidated by setting to null
50 * which will cause it to be recomputed.
51 */
52 protected String encodedPersonal;
53
54 /**
55 * The personal name as a Java String.
56 * Subclasses must ensure that this field is updated if the encodedPersonal field
57 * is updated; alternatively, it can be invalidated by setting to null
58 * which will cause it to be recomputed.
59 */
60 protected String personal;
61
62 public InternetAddress() {
63 }
64
65 public InternetAddress(String address) throws AddressException {
66 this(address, true);
67 }
68
69 public InternetAddress(String address, boolean strict) throws AddressException {
70
71
72
73 AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
74
75 InternetAddress parsedAddress = parser.parseAddress();
76
77
78 this.address = parsedAddress.address;
79 this.personal = parsedAddress.personal;
80 this.encodedPersonal = parsedAddress.encodedPersonal;
81 }
82
83 public InternetAddress(String address, String personal) throws UnsupportedEncodingException {
84 this(address, personal, null);
85 }
86
87 public InternetAddress(String address, String personal, String charset) throws UnsupportedEncodingException {
88 this.address = address;
89 setPersonal(personal, charset);
90 }
91
92 /**
93 * Clone this object.
94 *
95 * @return a copy of this object as created by Object.clone()
96 */
97 public Object clone() {
98 try {
99 return super.clone();
100 } catch (CloneNotSupportedException e) {
101 throw new Error();
102 }
103 }
104
105 /**
106 * Return the type of this address.
107 *
108 * @return the type of this address; always "rfc822"
109 */
110 public String getType() {
111 return "rfc822";
112 }
113
114 /**
115 * Set the address.
116 * No validation is performed; validate() can be used to check if it is valid.
117 *
118 * @param address the address to set
119 */
120 public void setAddress(String address) {
121 this.address = address;
122 }
123
124 /**
125 * Set the personal name.
126 * The name is first checked to see if it can be encoded; if this fails then an
127 * UnsupportedEncodingException is thrown and no fields are modified.
128 *
129 * @param name the new personal name
130 * @param charset the charset to use; see {@link MimeUtility#encodeWord(String, String, String) MimeUtilityencodeWord}
131 * @throws UnsupportedEncodingException if the name cannot be encoded
132 */
133 public void setPersonal(String name, String charset) throws UnsupportedEncodingException {
134 personal = name;
135 if (name != null) {
136 encodedPersonal = MimeUtility.encodeWord(name, charset, null);
137 }
138 else {
139 encodedPersonal = null;
140 }
141 }
142
143 /**
144 * Set the personal name.
145 * The name is first checked to see if it can be encoded using {@link MimeUtility#encodeWord(String)}; if this fails then an
146 * UnsupportedEncodingException is thrown and no fields are modified.
147 *
148 * @param name the new personal name
149 * @throws UnsupportedEncodingException if the name cannot be encoded
150 */
151 public void setPersonal(String name) throws UnsupportedEncodingException {
152 personal = name;
153 if (name != null) {
154 encodedPersonal = MimeUtility.encodeWord(name);
155 }
156 else {
157 encodedPersonal = null;
158 }
159 }
160
161 /**
162 * Return the address.
163 *
164 * @return the address
165 */
166 public String getAddress() {
167 return address;
168 }
169
170 /**
171 * Return the personal name.
172 * If the personal field is null, then an attempt is made to decode the encodedPersonal
173 * field using {@link MimeUtility#decodeWord(String)}; if this is sucessful, then
174 * the personal field is updated with that value and returned; if there is a problem
175 * decoding the text then the raw value from encodedPersonal is returned.
176 *
177 * @return the personal name
178 */
179 public String getPersonal() {
180 if (personal == null && encodedPersonal != null) {
181 try {
182 personal = MimeUtility.decodeWord(encodedPersonal);
183 } catch (ParseException e) {
184 return encodedPersonal;
185 } catch (UnsupportedEncodingException e) {
186 return encodedPersonal;
187 }
188 }
189 return personal;
190 }
191
192 /**
193 * Return the encoded form of the personal name.
194 * If the encodedPersonal field is null, then an attempt is made to encode the
195 * personal field using {@link MimeUtility#encodeWord(String)}; if this is
196 * successful then the encodedPersonal field is updated with that value and returned;
197 * if there is a problem encoding the text then null is returned.
198 *
199 * @return the encoded form of the personal name
200 */
201 private String getEncodedPersonal() {
202 if (encodedPersonal == null && personal != null) {
203 try {
204 encodedPersonal = MimeUtility.encodeWord(personal);
205 } catch (UnsupportedEncodingException e) {
206
207 return null;
208 }
209 }
210 return encodedPersonal;
211 }
212
213
214 /**
215 * Return a string representation of this address using only US-ASCII characters.
216 *
217 * @return a string representation of this address
218 */
219 public String toString() {
220
221 if (isGroup()) {
222 return address;
223 }
224
225
226
227
228
229
230 String p = getEncodedPersonal();
231 if (p == null) {
232 return formatAddress(address);
233 }
234 else {
235 StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3);
236 buf.append(AddressParser.quoteString(p));
237 buf.append(" <").append(address).append(">");
238 return buf.toString();
239 }
240 }
241
242 /**
243 * Check the form of an address, and enclose it within brackets
244 * if they are required for this address form.
245 *
246 * @param a The source address.
247 *
248 * @return A formatted address, which can be the original address string.
249 */
250 private String formatAddress(String a)
251 {
252
253 if (address.endsWith(";") && address.indexOf(":") > 0) {
254 return address;
255 }
256
257 if (AddressParser.containsCharacters(a, "()<>,;:\"[]")) {
258 StringBuffer buf = new StringBuffer(address.length() + 3);
259 buf.append("<").append(address).append(">");
260 return buf.toString();
261 }
262 return address;
263 }
264
265 /**
266 * Return a string representation of this address using Unicode characters.
267 *
268 * @return a string representation of this address
269 */
270 public String toUnicodeString() {
271
272 if (isGroup()) {
273 return address;
274 }
275
276
277
278
279
280
281
282
283
284
285 String p = getPersonal();
286 if (p == null) {
287 return formatAddress(address);
288 }
289 else {
290 StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3);
291 buf.append(AddressParser.quoteString(p));
292 buf.append(" <").append(address).append(">");
293 return buf.toString();
294 }
295 }
296
297 /**
298 * Compares two addresses for equality.
299 * We define this as true if the other object is an InternetAddress
300 * and the two values returned by getAddress() are equal in a
301 * case-insensitive comparison.
302 *
303 * @param o the other object
304 * @return true if the addresses are the same
305 */
306 public boolean equals(Object o) {
307 if (this == o) return true;
308 if (!(o instanceof InternetAddress)) return false;
309
310 InternetAddress other = (InternetAddress) o;
311 String myAddress = getAddress();
312 return myAddress == null ? (other.getAddress() == null) : myAddress.equalsIgnoreCase(other.getAddress());
313 }
314
315 /**
316 * Return the hashCode for this address.
317 * We define this to be the hashCode of the address after conversion to lowercase.
318 *
319 * @return a hashCode for this address
320 */
321 public int hashCode() {
322 return (address == null) ? 0 : address.toLowerCase().hashCode();
323 }
324
325 /**
326 * Return true is this address is an RFC822 group address in the format
327 * <code>phrase ":" [#mailbox] ";"</code>.
328 * We check this by using the presense of a ':' character in the address, and a
329 * ';' as the very last character.
330 *
331 * @return true is this address represents a group
332 */
333 public boolean isGroup() {
334 if (address == null) {
335 return false;
336 }
337
338 return address.endsWith(";") && address.indexOf(":") > 0;
339 }
340
341 /**
342 * Return the members of a group address.
343 *
344 * If strict is true and the address does not contain an initial phrase then an AddressException is thrown.
345 * Otherwise the phrase is skipped and the remainder of the address is checked to see if it is a group.
346 * If it is, the content and strict flag are passed to parseHeader to extract the list of addresses;
347 * if it is not a group then null is returned.
348 *
349 * @param strict whether strict RFC822 checking should be performed
350 * @return an array of InternetAddress objects for the group members, or null if this address is not a group
351 * @throws AddressException if there was a problem parsing the header
352 */
353 public InternetAddress[] getGroup(boolean strict) throws AddressException {
354 if (address == null) {
355 return null;
356 }
357
358
359 AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
360 return parser.extractGroupList();
361 }
362
363 /**
364 * Return an InternetAddress representing the current user.
365 * <P/>
366 * If session is not null, we first look for an address specified in its
367 * "mail.from" property; if this is not set, we look at its "mail.user"
368 * and "mail.host" properties and if both are not null then an address of
369 * the form "${mail.user}@${mail.host}" is created.
370 * If this fails to give an address, then an attempt is made to create
371 * an address by combining the value of the "user.name" System property
372 * with the value returned from InetAddress.getLocalHost().getHostName().
373 * Any SecurityException raised accessing the system property or any
374 * UnknownHostException raised getting the hostname are ignored.
375 * <P/>
376 * Finally, an attempt is made to convert the value obtained above to
377 * an InternetAddress. If this fails, then null is returned.
378 *
379 * @param session used to obtain mail properties
380 * @return an InternetAddress for the current user, or null if it cannot be determined
381 */
382 public static InternetAddress getLocalAddress(Session session) {
383 String host = null;
384 String user = null;
385
386
387
388
389 if (session != null) {
390 String address = session.getProperty("mail.from");
391
392 if (address != null) {
393 try {
394 return new InternetAddress(address);
395 } catch (AddressException e) {
396
397 return null;
398 }
399 }
400
401
402
403 user = session.getProperty("mail.user");
404 host = session.getProperty("mail.host");
405 }
406
407 try {
408
409
410 if (user == null) {
411 user = System.getProperty("user.name");
412 }
413
414 if (host == null) {
415 host = InetAddress.getLocalHost().getHostName();
416 }
417
418 if (user != null && host != null) {
419
420 return new InternetAddress(user + '@' + host);
421 }
422 } catch (AddressException e) {
423
424 } catch (UnknownHostException e) {
425
426 } catch (SecurityException e) {
427
428 }
429 return null;
430 }
431
432 /**
433 * Convert the supplied addresses into a single String of comma-separated text as
434 * produced by {@link InternetAddress#toString() toString()}.
435 * No line-break detection is performed.
436 *
437 * @param addresses the array of addresses to convert
438 * @return a one-line String of comma-separated addresses
439 */
440 public static String toString(Address[] addresses) {
441 if (addresses == null || addresses.length == 0) {
442 return null;
443 }
444 if (addresses.length == 1) {
445 return addresses[0].toString();
446 } else {
447 StringBuffer buf = new StringBuffer(addresses.length * 32);
448 buf.append(addresses[0].toString());
449 for (int i = 1; i < addresses.length; i++) {
450 buf.append(", ");
451 buf.append(addresses[i].toString());
452 }
453 return buf.toString();
454 }
455 }
456
457 /**
458 * Convert the supplies addresses into a String of comma-separated text,
459 * inserting line-breaks between addresses as needed to restrict the line
460 * length to 72 characters. Splits will only be introduced between addresses
461 * so an address longer than 71 characters will still be placed on a single
462 * line.
463 *
464 * @param addresses the array of addresses to convert
465 * @param used the starting column
466 * @return a String of comma-separated addresses with optional line breaks
467 */
468 public static String toString(Address[] addresses, int used) {
469 if (addresses == null || addresses.length == 0) {
470 return null;
471 }
472 if (addresses.length == 1) {
473 String s = addresses[0].toString();
474 if (used + s.length() > 72) {
475 s = "\r\n " + s;
476 }
477 return s;
478 } else {
479 StringBuffer buf = new StringBuffer(addresses.length * 32);
480 for (int i = 0; i < addresses.length; i++) {
481 String s = addresses[1].toString();
482 if (i == 0) {
483 if (used + s.length() + 1 > 72) {
484 buf.append("\r\n ");
485 used = 2;
486 }
487 } else {
488 if (used + s.length() + 1 > 72) {
489 buf.append(",\r\n ");
490 used = 2;
491 } else {
492 buf.append(", ");
493 used += 2;
494 }
495 }
496 buf.append(s);
497 used += s.length();
498 }
499 return buf.toString();
500 }
501 }
502
503 /**
504 * Parse addresses out of the string with basic checking.
505 *
506 * @param addresses the addresses to parse
507 * @return an array of InternetAddresses parsed from the string
508 * @throws AddressException if addresses checking fails
509 */
510 public static InternetAddress[] parse(String addresses) throws AddressException {
511 return parse(addresses, true);
512 }
513
514 /**
515 * Parse addresses out of the string.
516 *
517 * @param addresses the addresses to parse
518 * @param strict if true perform detailed checking, if false just perform basic checking
519 * @return an array of InternetAddresses parsed from the string
520 * @throws AddressException if address checking fails
521 */
522 public static InternetAddress[] parse(String addresses, boolean strict) throws AddressException {
523 return parse(addresses, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
524 }
525
526 /**
527 * Parse addresses out of the string.
528 *
529 * @param addresses the addresses to parse
530 * @param strict if true perform detailed checking, if false perform little checking
531 * @return an array of InternetAddresses parsed from the string
532 * @throws AddressException if address checking fails
533 */
534 public static InternetAddress[] parseHeader(String addresses, boolean strict) throws AddressException {
535 return parse(addresses, strict ? AddressParser.STRICT : AddressParser.PARSE_HEADER);
536 }
537
538 /**
539 * Parse addresses with increasing degrees of RFC822 compliance checking.
540 *
541 * @param addresses the string to parse
542 * @param level The required strictness level.
543 *
544 * @return an array of InternetAddresses parsed from the string
545 * @throws AddressException
546 * if address checking fails
547 */
548 private static InternetAddress[] parse(String addresses, int level) throws AddressException {
549
550 AddressParser parser = new AddressParser(addresses, level);
551 return parser.parseAddressList();
552 }
553
554 /**
555 * Validate the address portion of an internet address to ensure
556 * validity. Throws an AddressException if any validity
557 * problems are encountered.
558 *
559 * @exception AddressException
560 */
561 public void validate() throws AddressException {
562
563
564 AddressParser parser = new AddressParser(formatAddress(address), AddressParser.STRICT);
565 parser.validateAddress();
566 }
567 }