Skip to content

Package: InternetAddress

InternetAddress

nameinstructionbranchcomplexitylinemethod
InternetAddress()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
InternetAddress(String)
M: 35 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
InternetAddress(String, String)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
InternetAddress(String, String, String)
M: 10 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
InternetAddress(String, boolean)
M: 19 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
_getLocalAddress(Session)
M: 87 C: 0
0%
M: 28 C: 0
0%
M: 15 C: 0
0%
M: 20 C: 0
0%
M: 1 C: 0
0%
checkAddress(String, boolean, boolean)
M: 386 C: 0
0%
M: 106 C: 0
0%
M: 54 C: 0
0%
M: 87 C: 0
0%
M: 1 C: 0
0%
clone()
M: 10 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
equals(Object)
M: 27 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
getAddress()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getGroup(boolean)
M: 35 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
getLocalAddress(Session)
M: 10 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
getLocalHostName()
M: 42 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
getPersonal()
M: 23 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
getType()
M: 2 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
hashCode()
M: 11 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
indexOfAny(String, String)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
indexOfAny(String, String, int)
M: 23 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 7 C: 0
0%
M: 1 C: 0
0%
isGroup()
M: 17 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
isInetAddressLiteral(String)
M: 58 C: 0
0%
M: 22 C: 0
0%
M: 12 C: 0
0%
M: 13 C: 0
0%
M: 1 C: 0
0%
isSimple()
M: 12 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
lengthOfFirstSegment(String)
M: 12 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
lengthOfLastSegment(String, int)
M: 19 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
parse(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
parse(String, boolean)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
parse(String, boolean, boolean)
M: 839 C: 0
0%
M: 233 C: 0
0%
M: 125 C: 0
0%
M: 265 C: 0
0%
M: 1 C: 0
0%
parseHeader(String, boolean)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
quotePhrase(String)
M: 110 C: 0
0%
M: 28 C: 0
0%
M: 15 C: 0
0%
M: 22 C: 0
0%
M: 1 C: 0
0%
setAddress(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setPersonal(String)
M: 14 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
setPersonal(String, String)
M: 16 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 21 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
toString()
M: 58 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
toString(Address[])
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
toString(Address[], int)
M: 75 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 18 C: 0
0%
M: 1 C: 0
0%
toUnicodeString()
M: 41 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
toUnicodeString(Address[])
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
toUnicodeString(Address[], int)
M: 106 C: 0
0%
M: 18 C: 0
0%
M: 10 C: 0
0%
M: 26 C: 0
0%
M: 1 C: 0
0%
unquote(String)
M: 65 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
validate()
M: 14 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package jakarta.mail.internet;
18:
19: import jakarta.mail.Address;
20: import jakarta.mail.Session;
21:
22: import java.io.UnsupportedEncodingException;
23: import java.net.InetAddress;
24: import java.net.UnknownHostException;
25: import java.nio.charset.StandardCharsets;
26: import java.util.ArrayList;
27: import java.util.List;
28: import java.util.Locale;
29: import java.util.StringTokenizer;
30:
31: /**
32: * This class represents an Internet email address using the syntax
33: * of <a href="http://www.ietf.org/rfc/rfc822.txt" target="_top">RFC822</a>.
34: * Typical address syntax is of the form "user@host.domain" or
35: * "Personal Name <user@host.domain>".
36: *
37: * @author Bill Shannon
38: * @author John Mani
39: */
40:
41: public class InternetAddress extends Address implements Cloneable {
42:
43: protected String address; // email address
44:
45: /**
46: * The personal name.
47: */
48: protected String personal;
49:
50: /**
51: * The RFC 2047 encoded version of the personal name. <p>
52: *
53: * This field and the <code>personal</code> field track each
54: * other, so if a subclass sets one of these fields directly, it
55: * should set the other to <code>null</code>, so that it is
56: * suitably recomputed.
57: */
58: protected String encodedPersonal;
59:
60: private static final long serialVersionUID = -7507595530758302903L;
61:
62: private static final boolean ignoreBogusGroupName =
63: MimeUtility.getBooleanSystemProperty(
64: "mail.mime.address.ignorebogusgroupname", true);
65:
66: private static final boolean useCanonicalHostName =
67: MimeUtility.getBooleanSystemProperty(
68: "mail.mime.address.usecanonicalhostname", true);
69:
70: private static final boolean allowUtf8 =
71: MimeUtility.getBooleanSystemProperty("mail.mime.allowutf8", false);
72:
73: /**
74: * Default constructor.
75: */
76: public InternetAddress() {
77: }
78:
79: /**
80: * Constructor. <p>
81: *
82: * Parse the given string and create an InternetAddress.
83: * See the <code>parse</code> method for details of the parsing.
84: * The address is parsed using "strict" parsing.
85: * This constructor does <b>not</b> perform the additional
86: * syntax checks that the
87: * <code>InternetAddress(String address, boolean strict)</code>
88: * constructor does when <code>strict</code> is <code>true</code>.
89: * This constructor is equivalent to
90: * <code>InternetAddress(address, false)</code>.
91: *
92: * @param address the address in RFC822 format
93: * @throws AddressException if the parse failed
94: */
95: public InternetAddress(String address) throws AddressException {
96: // use our address parsing utility routine to parse the string
97: InternetAddress[] a = parse(address, true);
98: // if we got back anything other than a single address, it's an error
99:• if (a.length != 1)
100: throw new AddressException("Illegal address", address);
101:
102: /*
103: * Now copy the contents of the single address we parsed
104: * into the current object, which will be returned from the
105: * constructor.
106: * XXX - this sure is a round-about way of getting this done.
107: */
108: this.address = a[0].address;
109: this.personal = a[0].personal;
110: this.encodedPersonal = a[0].encodedPersonal;
111: }
112:
113: /**
114: * Parse the given string and create an InternetAddress.
115: * If <code>strict</code> is false, the detailed syntax of the
116: * address isn't checked.
117: *
118: * @param address the address in RFC822 format
119: * @param strict enforce RFC822 syntax
120: * @throws AddressException if the parse failed
121: * @since JavaMail 1.3
122: */
123: public InternetAddress(String address, boolean strict)
124: throws AddressException {
125: this(address);
126:• if (strict) {
127:• if (isGroup())
128: getGroup(true); // throw away the result
129: else
130: checkAddress(this.address, true, true);
131: }
132: }
133:
134: /**
135: * Construct an InternetAddress given the address and personal name.
136: * The address is assumed to be a syntactically valid RFC822 address.
137: *
138: * @param address the address in RFC822 format
139: * @param personal the personal name
140: * @throws UnsupportedEncodingException if the personal name
141: * can't be encoded in the given charset
142: */
143: public InternetAddress(String address, String personal)
144: throws UnsupportedEncodingException {
145: this(address, personal, null);
146: }
147:
148: /**
149: * Construct an InternetAddress given the address and personal name.
150: * The address is assumed to be a syntactically valid RFC822 address.
151: *
152: * @param address the address in RFC822 format
153: * @param personal the personal name
154: * @param charset the MIME charset for the name
155: * @throws UnsupportedEncodingException if the personal name
156: * can't be encoded in the given charset
157: */
158: public InternetAddress(String address, String personal, String charset)
159: throws UnsupportedEncodingException {
160: this.address = address;
161: setPersonal(personal, charset);
162: }
163:
164: /**
165: * Return a copy of this InternetAddress object.
166: *
167: * @since JavaMail 1.2
168: */
169: @Override
170: public Object clone() {
171: InternetAddress a = null;
172: try {
173: a = (InternetAddress) super.clone();
174: } catch (CloneNotSupportedException e) {
175: } // Won't happen
176: return a;
177: }
178:
179: /**
180: * Return the type of this address. The type of an InternetAddress
181: * is "rfc822".
182: */
183: @Override
184: public String getType() {
185: return "rfc822";
186: }
187:
188: /**
189: * Set the email address.
190: *
191: * @param address email address
192: */
193: public void setAddress(String address) {
194: this.address = address;
195: }
196:
197: /**
198: * Set the personal name. If the name contains non US-ASCII
199: * characters, then the name will be encoded using the specified
200: * charset as per RFC 2047. If the name contains only US-ASCII
201: * characters, no encoding is done and the name is used as is.
202: *
203: * @param name personal name
204: * @param charset MIME charset to be used to encode the name as
205: * per RFC 2047
206: * @throws UnsupportedEncodingException if the charset encoding
207: * fails.
208: * @see #setPersonal(String)
209: */
210: public void setPersonal(String name, String charset)
211: throws UnsupportedEncodingException {
212: personal = name;
213:• if (name != null)
214: encodedPersonal = MimeUtility.encodeWord(name, charset, null);
215: else
216: encodedPersonal = null;
217: }
218:
219: /**
220: * Set the personal name. If the name contains non US-ASCII
221: * characters, then the name will be encoded using the platform's
222: * default charset. If the name contains only US-ASCII characters,
223: * no encoding is done and the name is used as is.
224: *
225: * @param name personal name
226: * @throws UnsupportedEncodingException if the charset encoding
227: * fails.
228: * @see #setPersonal(String name, String charset)
229: */
230: public void setPersonal(String name)
231: throws UnsupportedEncodingException {
232: personal = name;
233:• if (name != null)
234: encodedPersonal = MimeUtility.encodeWord(name);
235: else
236: encodedPersonal = null;
237: }
238:
239: /**
240: * Get the email address.
241: *
242: * @return email address
243: */
244: public String getAddress() {
245: return address;
246: }
247:
248: /**
249: * Get the personal name. If the name is encoded as per RFC 2047,
250: * it is decoded and converted into Unicode. If the decoding or
251: * conversion fails, the raw data is returned as is.
252: *
253: * @return personal name
254: */
255: public String getPersonal() {
256:• if (personal != null)
257: return personal;
258:
259:• if (encodedPersonal != null) {
260: try {
261: personal = MimeUtility.decodeText(encodedPersonal);
262: return personal;
263: } catch (Exception ex) {
264: // 1. ParseException: either its an unencoded string or
265: //        it can't be parsed
266: // 2. UnsupportedEncodingException: can't decode it.
267: return encodedPersonal;
268: }
269: }
270: // No personal or encodedPersonal, return null
271: return null;
272: }
273:
274: /**
275: * Convert this address into a RFC 822 / RFC 2047 encoded address.
276: * The resulting string contains only US-ASCII characters, and
277: * hence is mail-safe.
278: *
279: * @return possibly encoded address string
280: */
281: @Override
282: public String toString() {
283:• String a = address == null ? "" : address;
284:• if (encodedPersonal == null && personal != null)
285: try {
286: encodedPersonal = MimeUtility.encodeWord(personal);
287: } catch (UnsupportedEncodingException ex) {
288: }
289:
290:• if (encodedPersonal != null)
291: return quotePhrase(encodedPersonal) + " <" + a + ">";
292:• else if (isGroup() || isSimple())
293: return a;
294: else
295: return "<" + a + ">";
296: }
297:
298: /**
299: * Returns a properly formatted address (RFC 822 syntax) of
300: * Unicode characters.
301: *
302: * @return Unicode address string
303: * @since JavaMail 1.2
304: */
305: public String toUnicodeString() {
306: String p = getPersonal();
307:• if (p != null)
308: return quotePhrase(p) + " <" + address + ">";
309:• else if (isGroup() || isSimple())
310: return address;
311: else
312: return "<" + address + ">";
313: }
314:
315: /*
316: * quotePhrase() quotes the words within a RFC822 phrase.
317: *
318: * This is tricky, since a phrase is defined as 1 or more
319: * RFC822 words, separated by LWSP. Now, a word that contains
320: * LWSP is supposed to be quoted, and this is exactly what the
321: * MimeUtility.quote() method does. However, when dealing with
322: * a phrase, any LWSP encountered can be construed to be the
323: * separator between words, and not part of the words themselves.
324: * To deal with this funkiness, we have the below variant of
325: * MimeUtility.quote(), which essentially ignores LWSP when
326: * deciding whether to quote a word.
327: *
328: * It aint pretty, but it gets the job done :)
329: */
330:
331: private static final String rfc822phrase =
332: HeaderTokenizer.RFC822.replace(' ', '\0').replace('\t', '\0');
333:
334: private static String quotePhrase(String phrase) {
335: int len = phrase.length();
336: boolean needQuoting = false;
337:
338:• for (int i = 0; i < len; i++) {
339: char c = phrase.charAt(i);
340:• if (c == '"' || c == '\\') {
341: // need to escape them and then quote the whole string
342: StringBuilder sb = new StringBuilder(len + 3);
343: sb.append('"');
344:• for (int j = 0; j < len; j++) {
345: char cc = phrase.charAt(j);
346:• if (cc == '"' || cc == '\\')
347: // Escape the character
348: sb.append('\\');
349: sb.append(cc);
350: }
351: sb.append('"');
352: return sb.toString();
353:• } else if ((c < 040 && c != '\r' && c != '\n' && c != '\t') ||
354:• (c >= 0177 && !allowUtf8) || rfc822phrase.indexOf(c) >= 0)
355: // These characters cause the string to be quoted
356: needQuoting = true;
357: }
358:
359:• if (needQuoting) {
360: StringBuilder sb = new StringBuilder(len + 2);
361: sb.append('"').append(phrase).append('"');
362: return sb.toString();
363: } else
364: return phrase;
365: }
366:
367: private static String unquote(String s) {
368:• if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) {
369: s = s.substring(1, s.length() - 1);
370: // check for any escaped characters
371:• if (s.indexOf('\\') >= 0) {
372: StringBuilder sb = new StringBuilder(s.length()); // approx
373:• for (int i = 0; i < s.length(); i++) {
374: char c = s.charAt(i);
375:• if (c == '\\' && i < s.length() - 1)
376: c = s.charAt(++i);
377: sb.append(c);
378: }
379: s = sb.toString();
380: }
381: }
382: return s;
383: }
384:
385: /**
386: * The equality operator.
387: */
388: @Override
389: public boolean equals(Object a) {
390:• if (!(a instanceof InternetAddress))
391: return false;
392:
393: String s = ((InternetAddress) a).getAddress();
394:• if (s == address)
395: return true;
396:• if (address != null && address.equalsIgnoreCase(s))
397: return true;
398:
399: return false;
400: }
401:
402: /**
403: * Compute a hash code for the address.
404: */
405: @Override
406: public int hashCode() {
407:• if (address == null)
408: return 0;
409: else
410: return address.toLowerCase(Locale.ENGLISH).hashCode();
411: }
412:
413: /**
414: * Convert the given array of InternetAddress objects into
415: * a comma separated sequence of address strings. The
416: * resulting string contains only US-ASCII characters, and
417: * hence is mail-safe.
418: *
419: * @param addresses array of InternetAddress objects
420: * @return comma separated string of addresses
421: * @throws ClassCastException if any address object in the
422: * given array is not an InternetAddress object. Note
423: * that this is a RuntimeException.
424: */
425: public static String toString(Address[] addresses) {
426: return toString(addresses, 0);
427: }
428:
429: /**
430: * Convert the given array of InternetAddress objects into
431: * a comma separated sequence of address strings. The
432: * resulting string contains Unicode characters.
433: *
434: * @param addresses array of InternetAddress objects
435: * @return comma separated string of addresses
436: * @throws ClassCastException if any address object in the
437: * given array is not an InternetAddress object. Note
438: * that this is a RuntimeException.
439: * @since JavaMail 1.6
440: */
441: public static String toUnicodeString(Address[] addresses) {
442: return toUnicodeString(addresses, 0);
443: }
444:
445: /**
446: * Convert the given array of InternetAddress objects into
447: * a comma separated sequence of address strings. The
448: * resulting string contains only US-ASCII characters, and
449: * hence is mail-safe. <p>
450: *
451: * The 'used' parameter specifies the number of character positions
452: * already taken up in the field into which the resulting address
453: * sequence string is to be inserted. It is used to determine the
454: * line-break positions in the resulting address sequence string.
455: *
456: * @param addresses array of InternetAddress objects
457: * @param used number of character positions already used, in
458: * the field into which the address string is to
459: * be inserted.
460: * @return comma separated string of addresses
461: * @throws ClassCastException if any address object in the
462: * given array is not an InternetAddress object. Note
463: * that this is a RuntimeException.
464: */
465: public static String toString(Address[] addresses, int used) {
466:• if (addresses == null || addresses.length == 0)
467: return null;
468:
469: StringBuilder sb = new StringBuilder();
470:
471:• for (int i = 0; i < addresses.length; i++) {
472:• if (i != 0) { // need to append comma
473: sb.append(", ");
474: used += 2;
475: }
476:
477: // prefer not to split a single address across lines so used=0 below
478: String s = MimeUtility.fold(0, addresses[i].toString());
479: int len = lengthOfFirstSegment(s); // length till CRLF
480:• if (used + len > 76) { // overflows ...
481: // smash trailing space from ", " above
482: int curlen = sb.length();
483:• if (curlen > 0 && sb.charAt(curlen - 1) == ' ')
484: sb.setLength(curlen - 1);
485: sb.append("\r\n\t"); // .. start new continuation line
486: used = 8; // account for the starting <tab> char
487: }
488: sb.append(s);
489: used = lengthOfLastSegment(s, used);
490: }
491:
492: return sb.toString();
493: }
494:
495: /**
496: * Convert the given array of InternetAddress objects into
497: * a comma separated sequence of address strings. The
498: * resulting string contains Unicode characters. <p>
499: *
500: * The 'used' parameter specifies the number of character positions
501: * already taken up in the field into which the resulting address
502: * sequence string is to be inserted. It is used to determine the
503: * line-break positions in the resulting address sequence string.
504: *
505: * @param addresses array of InternetAddress objects
506: * @param used number of character positions already used, in
507: * the field into which the address string is to
508: * be inserted.
509: * @return comma separated string of addresses
510: * @throws ClassCastException if any address object in the
511: * given array is not an InternetAddress object. Note
512: * that this is a RuntimeException.
513: * @since JavaMail 1.6
514: */
515: /*
516: * XXX - This is exactly the same as the above, except it uses
517: *         toUnicodeString instead of toString.
518: * XXX - Since the line length restrictions are in bytes, not characters,
519: *         we convert all non-ASCII addresses to UTF-8 byte strings,
520: *         which we then convert to ISO-8859-1 Strings where every
521: *         character respresents one UTF-8 byte. At the end we reverse
522: *         the conversion to get back to a correct Unicode string.
523: *         This is a hack to allow all the other character-based methods
524: *         to work properly with UTF-8 bytes.
525: */
526: public static String toUnicodeString(Address[] addresses, int used) {
527:• if (addresses == null || addresses.length == 0)
528: return null;
529:
530: StringBuilder sb = new StringBuilder();
531:
532: boolean sawNonAscii = false;
533:• for (int i = 0; i < addresses.length; i++) {
534:• if (i != 0) { // need to append comma
535: sb.append(", ");
536: used += 2;
537: }
538:
539: // prefer not to split a single address across lines so used=0 below
540: String as = ((InternetAddress) addresses[i]).toUnicodeString();
541:• if (MimeUtility.checkAscii(as) != MimeUtility.ALL_ASCII) {
542: sawNonAscii = true;
543: as = new String(as.getBytes(StandardCharsets.UTF_8),
544: StandardCharsets.ISO_8859_1);
545: }
546: String s = MimeUtility.fold(0, as);
547: int len = lengthOfFirstSegment(s); // length till CRLF
548:• if (used + len > 76) { // overflows ...
549: // smash trailing space from ", " above
550: int curlen = sb.length();
551:• if (curlen > 0 && sb.charAt(curlen - 1) == ' ')
552: sb.setLength(curlen - 1);
553: sb.append("\r\n\t"); // .. start new continuation line
554: used = 8; // account for the starting <tab> char
555: }
556: sb.append(s);
557: used = lengthOfLastSegment(s, used);
558: }
559:
560: String ret = sb.toString();
561:• if (sawNonAscii)
562: ret = new String(ret.getBytes(StandardCharsets.ISO_8859_1),
563: StandardCharsets.UTF_8);
564: return ret;
565: }
566:
567: /*
568: * Return the length of the first segment within this string.
569: * If no segments exist, the length of the whole line is returned.
570: */
571: private static int lengthOfFirstSegment(String s) {
572: int pos;
573:• if ((pos = s.indexOf("\r\n")) != -1)
574: return pos;
575: else
576: return s.length();
577: }
578:
579: /*
580: * Return the length of the last segment within this string.
581: * If no segments exist, the length of the whole line plus
582: * <code>used</code> is returned.
583: */
584: private static int lengthOfLastSegment(String s, int used) {
585: int pos;
586:• if ((pos = s.lastIndexOf("\r\n")) != -1)
587: return s.length() - pos - 2;
588: else
589: return s.length() + used;
590: }
591:
592: /**
593: * Return an InternetAddress object representing the current user.
594: * The entire email address may be specified in the "mail.from"
595: * property. If not set, the "mail.user" and "mail.host" properties
596: * are tried. If those are not set, the "user.name" property and
597: * <code>InetAddress.getLocalHost</code> method are tried.
598: * Security exceptions that may occur while accessing this information
599: * are ignored. If it is not possible to determine an email address,
600: * null is returned.
601: *
602: * @param session Session object used for property lookup
603: * @return current user's email address
604: */
605: public static InternetAddress getLocalAddress(Session session) {
606: try {
607: return _getLocalAddress(session);
608: } catch (SecurityException sex) { // ignore it
609: } catch (AddressException ex) { // ignore it
610: } catch (UnknownHostException ex) {
611: } // ignore it
612: return null;
613: }
614:
615: /**
616: * A package-private version of getLocalAddress that doesn't swallow
617: * the exception. Used by MimeMessage.setFrom() to report the reason
618: * for the failure.
619: */
620: // package-private
621: static InternetAddress _getLocalAddress(Session session)
622: throws SecurityException, AddressException, UnknownHostException {
623: String user = null, host = null, address = null;
624:• if (session == null) {
625: user = System.getProperty("user.name");
626: host = getLocalHostName();
627: } else {
628: address = session.getProperty("mail.from");
629:• if (address == null) {
630: user = session.getProperty("mail.user");
631:• if (user == null || user.length() == 0)
632: user = session.getProperty("user.name");
633:• if (user == null || user.length() == 0)
634: user = System.getProperty("user.name");
635: host = session.getProperty("mail.host");
636:• if (host == null || host.length() == 0)
637: host = getLocalHostName();
638: }
639: }
640:
641:• if (address == null && user != null && user.length() != 0 &&
642:• host != null && host.length() != 0)
643: address = MimeUtility.quote(user.trim(), specialsNoDot + "\t ") +
644: "@" + host;
645:
646:• if (address == null)
647: return null;
648:
649: return new InternetAddress(address);
650: }
651:
652: /**
653: * Get the local host name from InetAddress and return it in a form
654: * suitable for use in an email address.
655: */
656: private static String getLocalHostName() throws UnknownHostException {
657: String host = null;
658: InetAddress me = InetAddress.getLocalHost();
659:• if (me != null) {
660: // try canonical host name first
661:• if (useCanonicalHostName)
662: host = me.getCanonicalHostName();
663:• if (host == null)
664: host = me.getHostName();
665: // if we can't get our name, use local address literal
666:• if (host == null)
667: host = me.getHostAddress();
668:• if (host != null && host.length() > 0 && isInetAddressLiteral(host))
669: host = '[' + host + ']';
670: }
671: return host;
672: }
673:
674: /**
675: * Is the address an IPv4 or IPv6 address literal, which needs to
676: * be enclosed in "[]" in an email address? IPv4 literals contain
677: * decimal digits and dots, IPv6 literals contain hex digits, dots,
678: * and colons. We're lazy and don't check the exact syntax, just
679: * the allowed characters; strings that have only the allowed
680: * characters in a literal but don't meet the syntax requirements
681: * for a literal definitely can't be a host name and thus will fail
682: * later when used as an address literal.
683: */
684: private static boolean isInetAddressLiteral(String addr) {
685: boolean sawHex = false, sawColon = false;
686:• for (int i = 0; i < addr.length(); i++) {
687: char c = addr.charAt(i);
688:• if (c >= '0' && c <= '9')
689: ; // digits always ok
690:• else if (c == '.')
691: ; // dot always ok
692:• else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
693: sawHex = true; // need to see a colon too
694:• else if (c == ':')
695: sawColon = true;
696: else
697: return false; // anything else, definitely not a literal
698: }
699:• return !sawHex || sawColon;
700: }
701:
702: /**
703: * Parse the given comma separated sequence of addresses into
704: * InternetAddress objects. Addresses must follow RFC822 syntax.
705: *
706: * @param addresslist comma separated address strings
707: * @return array of InternetAddress objects
708: * @throws AddressException if the parse failed
709: */
710: public static InternetAddress[] parse(String addresslist)
711: throws AddressException {
712: return parse(addresslist, true);
713: }
714:
715: /**
716: * Parse the given sequence of addresses into InternetAddress
717: * objects. If <code>strict</code> is false, simple email addresses
718: * separated by spaces are also allowed. If <code>strict</code> is
719: * true, many (but not all) of the RFC822 syntax rules are enforced.
720: * In particular, even if <code>strict</code> is true, addresses
721: * composed of simple names (with no "@domain" part) are allowed.
722: * Such "illegal" addresses are not uncommon in real messages. <p>
723: *
724: * Non-strict parsing is typically used when parsing a list of
725: * mail addresses entered by a human. Strict parsing is typically
726: * used when parsing address headers in mail messages.
727: *
728: * @param addresslist comma separated address strings
729: * @param strict enforce RFC822 syntax
730: * @return array of InternetAddress objects
731: * @throws AddressException if the parse failed
732: */
733: public static InternetAddress[] parse(String addresslist, boolean strict)
734: throws AddressException {
735: return parse(addresslist, strict, false);
736: }
737:
738: /**
739: * Parse the given sequence of addresses into InternetAddress
740: * objects. If <code>strict</code> is false, the full syntax rules for
741: * individual addresses are not enforced. If <code>strict</code> is
742: * true, many (but not all) of the RFC822 syntax rules are enforced. <p>
743: *
744: * To better support the range of "invalid" addresses seen in real
745: * messages, this method enforces fewer syntax rules than the
746: * <code>parse</code> method when the strict flag is false
747: * and enforces more rules when the strict flag is true. If the
748: * strict flag is false and the parse is successful in separating out an
749: * email address or addresses, the syntax of the addresses themselves
750: * is not checked.
751: *
752: * @param addresslist comma separated address strings
753: * @param strict enforce RFC822 syntax
754: * @return array of InternetAddress objects
755: * @throws AddressException if the parse failed
756: * @since JavaMail 1.3
757: */
758: public static InternetAddress[] parseHeader(String addresslist,
759: boolean strict) throws AddressException {
760: return parse(MimeUtility.unfold(addresslist), strict, true);
761: }
762:
763: /*
764: * RFC822 Address parser.
765: *
766: * XXX - This is complex enough that it ought to be a real parser,
767: * not this ad-hoc mess, and because of that, this is not perfect.
768: *
769: * XXX - Deal with encoded Headers too.
770: */
771: @SuppressWarnings("fallthrough")
772: private static InternetAddress[] parse(String s, boolean strict,
773: boolean parseHdr) throws AddressException {
774: int start, end, index, nesting;
775: int start_personal = -1, end_personal = -1;
776: int length = s.length();
777:• boolean ignoreErrors = parseHdr && !strict;
778: boolean in_group = false; // we're processing a group term
779: boolean route_addr = false; // address came from route-addr term
780: boolean rfc822 = false; // looks like an RFC822 address
781: char c;
782: List<InternetAddress> v = new ArrayList<>();
783: InternetAddress ma;
784:
785:• for (start = end = -1, index = 0; index < length; index++) {
786: c = s.charAt(index);
787:
788:• switch (c) {
789: case '(': // We are parsing a Comment. Ignore everything inside.
790: // XXX - comment fields should be parsed as whitespace,
791: //         more than one allowed per address
792: rfc822 = true;
793:• if (start >= 0 && end == -1)
794: end = index;
795: int pindex = index;
796:• for (index++, nesting = 1; index < length && nesting > 0;
797: index++) {
798: c = s.charAt(index);
799:• switch (c) {
800: case '\\':
801: index++; // skip both '\' and the escaped char
802: break;
803: case '(':
804: nesting++;
805: break;
806: case ')':
807: nesting--;
808: break;
809: default:
810: break;
811: }
812: }
813:• if (nesting > 0) {
814:• if (!ignoreErrors)
815: throw new AddressException("Missing ')'", s, index);
816: // pretend the first paren was a regular character and
817: // continue parsing after it
818: index = pindex + 1;
819: break;
820: }
821: index--; // point to closing paren
822:• if (start_personal == -1)
823: start_personal = pindex + 1;
824:• if (end_personal == -1)
825: end_personal = index;
826: break;
827:
828: case ')':
829:• if (!ignoreErrors)
830: throw new AddressException("Missing '('", s, index);
831: // pretend the left paren was a regular character and
832: // continue parsing
833:• if (start == -1)
834: start = index;
835: break;
836:
837: case '<':
838: rfc822 = true;
839:• if (route_addr) {
840:• if (!ignoreErrors)
841: throw new AddressException(
842: "Extra route-addr", s, index);
843:
844: // assume missing comma between addresses
845:• if (start == -1) {
846: route_addr = false;
847: rfc822 = false;
848: start = end = -1;
849: break; // nope, nothing there
850: }
851:• if (!in_group) {
852: // got a token, add this to our InternetAddress list
853:• if (end == -1) // should never happen
854: end = index;
855: String addr = s.substring(start, end).trim();
856:
857: ma = new InternetAddress();
858: ma.setAddress(addr);
859:• if (start_personal >= 0) {
860: ma.encodedPersonal = unquote(
861: s.substring(start_personal, end_personal).
862: trim());
863: }
864: v.add(ma);
865:
866: route_addr = false;
867: rfc822 = false;
868: start = end = -1;
869: start_personal = end_personal = -1;
870: // continue processing this new address...
871: }
872: }
873:
874: int rindex = index;
875: boolean inquote = false;
876: outf:
877:• for (index++; index < length; index++) {
878: c = s.charAt(index);
879:• switch (c) {
880: case '\\': // XXX - is this needed?
881: index++; // skip both '\' and the escaped char
882: break;
883: case '"':
884:• inquote = !inquote;
885: break;
886: case '>':
887:• if (inquote)
888: continue;
889: break outf; // out of for loop
890: default:
891: break;
892: }
893: }
894:
895: // did we find a matching quote?
896:• if (inquote) {
897:• if (!ignoreErrors)
898: throw new AddressException("Missing '\"'", s, index);
899: // didn't find matching quote, try again ignoring quotes
900: // (e.g., ``<"@foo.com>'')
901: outq:
902:• for (index = rindex + 1; index < length; index++) {
903: c = s.charAt(index);
904:• if (c == '\\') // XXX - is this needed?
905: index++; // skip both '\' and the escaped char
906:• else if (c == '>')
907: break;
908: }
909: }
910:
911: // did we find a terminating '>'?
912:• if (index >= length) {
913:• if (!ignoreErrors)
914: throw new AddressException("Missing '>'", s, index);
915: // pretend the "<" was a regular character and
916: // continue parsing after it (e.g., ``<@foo.com'')
917: index = rindex + 1;
918:• if (start == -1)
919: start = rindex; // back up to include "<"
920: break;
921: }
922:
923:• if (!in_group) {
924:• if (start >= 0) {
925: // seen some characters? use them as the personal name
926: start_personal = start;
927: end_personal = rindex;
928: }
929: start = rindex + 1;
930: }
931: route_addr = true;
932: end = index;
933: break;
934:
935: case '>':
936:• if (!ignoreErrors)
937: throw new AddressException("Missing '<'", s, index);
938: // pretend the ">" was a regular character and
939: // continue parsing (e.g., ``>@foo.com'')
940:• if (start == -1)
941: start = index;
942: break;
943:
944: case '"': // parse quoted string
945: int qindex = index;
946: rfc822 = true;
947:• if (start == -1)
948: start = index;
949: outq:
950:• for (index++; index < length; index++) {
951: c = s.charAt(index);
952:• switch (c) {
953: case '\\':
954: index++; // skip both '\' and the escaped char
955: break;
956: case '"':
957: break outq; // out of for loop
958: default:
959: break;
960: }
961: }
962:• if (index >= length) {
963:• if (!ignoreErrors)
964: throw new AddressException("Missing '\"'", s, index);
965: // pretend the quote was a regular character and
966: // continue parsing after it (e.g., ``"@foo.com'')
967: index = qindex + 1;
968: }
969: break;
970:
971: case '[': // a domain-literal, probably
972: int lindex = index;
973: rfc822 = true;
974:• if (start == -1)
975: start = index;
976: outb:
977:• for (index++; index < length; index++) {
978: c = s.charAt(index);
979:• switch (c) {
980: case '\\':
981: index++; // skip both '\' and the escaped char
982: break;
983: case ']':
984: break outb; // out of for loop
985: default:
986: break;
987: }
988: }
989:• if (index >= length) {
990:• if (!ignoreErrors)
991: throw new AddressException("Missing ']'", s, index);
992: // pretend the "[" was a regular character and
993: // continue parsing after it (e.g., ``[@foo.com'')
994: index = lindex + 1;
995: }
996: break;
997:
998: case ';':
999:• if (start == -1) {
1000: route_addr = false;
1001: rfc822 = false;
1002: start = end = -1;
1003: break; // nope, nothing there
1004: }
1005:• if (in_group) {
1006: in_group = false;
1007: /*
1008: * If parsing headers, but not strictly, peek ahead.
1009: * If next char is "@", treat the group name
1010: * like the local part of the address, e.g.,
1011: * "Undisclosed-Recipient:;@java.sun.com".
1012: */
1013:• if (parseHdr && !strict &&
1014:• index + 1 < length && s.charAt(index + 1) == '@')
1015: break;
1016: ma = new InternetAddress();
1017: end = index + 1;
1018: ma.setAddress(s.substring(start, end).trim());
1019: v.add(ma);
1020:
1021: route_addr = false;
1022: rfc822 = false;
1023: start = end = -1;
1024: start_personal = end_personal = -1;
1025: break;
1026: }
1027:• if (!ignoreErrors)
1028: throw new AddressException(
1029: "Illegal semicolon, not in group", s, index);
1030:
1031: // otherwise, parsing a header; treat semicolon like comma
1032: // fall through to comma case...
1033:
1034: case ',': // end of an address, probably
1035:• if (start == -1) {
1036: route_addr = false;
1037: rfc822 = false;
1038: start = end = -1;
1039: break; // nope, nothing there
1040: }
1041:• if (in_group) {
1042: route_addr = false;
1043: break;
1044: }
1045: // got a token, add this to our InternetAddress list
1046:• if (end == -1)
1047: end = index;
1048:
1049: String addr = s.substring(start, end).trim();
1050: String pers = null;
1051:• if (rfc822 && start_personal >= 0) {
1052: pers = unquote(
1053: s.substring(start_personal, end_personal).trim());
1054:• if (pers.trim().length() == 0)
1055: pers = null;
1056: }
1057:
1058: /*
1059: * If the personal name field has an "@" and the address
1060: * field does not, assume they were reversed, e.g.,
1061: * ``"joe doe" (john.doe@example.com)''.
1062: */
1063:• if (parseHdr && !strict && pers != null &&
1064:• pers.indexOf('@') >= 0 &&
1065:• addr.indexOf('@') < 0 && addr.indexOf('!') < 0) {
1066: String tmp = addr;
1067: addr = pers;
1068: pers = tmp;
1069: }
1070:• if (rfc822 || strict || parseHdr) {
1071:• if (!ignoreErrors)
1072: checkAddress(addr, route_addr, false);
1073: ma = new InternetAddress();
1074: ma.setAddress(addr);
1075:• if (pers != null)
1076: ma.encodedPersonal = pers;
1077: v.add(ma);
1078: } else {
1079: // maybe we passed over more than one space-separated addr
1080: StringTokenizer st = new StringTokenizer(addr);
1081:• while (st.hasMoreTokens()) {
1082: String a = st.nextToken();
1083: checkAddress(a, false, false);
1084: ma = new InternetAddress();
1085: ma.setAddress(a);
1086: v.add(ma);
1087: }
1088: }
1089:
1090: route_addr = false;
1091: rfc822 = false;
1092: start = end = -1;
1093: start_personal = end_personal = -1;
1094: break;
1095:
1096: case ':':
1097: rfc822 = true;
1098:• if (in_group)
1099:• if (!ignoreErrors)
1100: throw new AddressException("Nested group", s, index);
1101:• if (start == -1)
1102: start = index;
1103:• if (parseHdr && !strict) {
1104: /*
1105: * If next char is a special character that can't occur at
1106: * the start of a valid address, treat the group name
1107: * as the entire address, e.g., "Date:, Tue", "Re:@foo".
1108: */
1109:• if (index + 1 < length) {
1110: String addressSpecials = ")>[]:@\\,.";
1111: char nc = s.charAt(index + 1);
1112:• if (addressSpecials.indexOf(nc) >= 0) {
1113:• if (nc != '@')
1114: break; // don't change in_group
1115: /*
1116: * Handle a common error:
1117: * ``Undisclosed-Recipient:@example.com;''
1118: *
1119: * Scan ahead. If we find a semicolon before
1120: * one of these other special characters,
1121: * consider it to be a group after all.
1122: */
1123:• for (int i = index + 2; i < length; i++) {
1124: nc = s.charAt(i);
1125:• if (nc == ';')
1126: break;
1127:• if (addressSpecials.indexOf(nc) >= 0)
1128: break;
1129: }
1130:• if (nc == ';')
1131: break; // don't change in_group
1132: }
1133: }
1134:
1135: // ignore bogus "mailto:" prefix in front of an address,
1136: // or bogus mail header name included in the address field
1137: String gname = s.substring(start, index);
1138:• if (ignoreBogusGroupName &&
1139:• (gname.equalsIgnoreCase("mailto") ||
1140:• gname.equalsIgnoreCase("From") ||
1141:• gname.equalsIgnoreCase("To") ||
1142:• gname.equalsIgnoreCase("Cc") ||
1143:• gname.equalsIgnoreCase("Subject") ||
1144:• gname.equalsIgnoreCase("Re")))
1145: start = -1; // we're not really in a group
1146: else
1147: in_group = true;
1148: } else
1149: in_group = true;
1150: break;
1151:
1152: // Ignore whitespace
1153: case ' ':
1154: case '\t':
1155: case '\r':
1156: case '\n':
1157: break;
1158:
1159: default:
1160:• if (start == -1)
1161: start = index;
1162: break;
1163: }
1164: }
1165:
1166:• if (start >= 0) {
1167: /*
1168: * The last token, add this to our InternetAddress list.
1169: * Note that this block of code should be identical to the
1170: * block above for "case ','".
1171: */
1172:• if (end == -1)
1173: end = length;
1174:
1175: String addr = s.substring(start, end).trim();
1176: String pers = null;
1177:• if (rfc822 && start_personal >= 0) {
1178: pers = unquote(
1179: s.substring(start_personal, end_personal).trim());
1180:• if (pers.trim().length() == 0)
1181: pers = null;
1182: }
1183:
1184: /*
1185: * If the personal name field has an "@" and the address
1186: * field does not, assume they were reversed, e.g.,
1187: * ``"joe doe" (john.doe@example.com)''.
1188: */
1189:• if (parseHdr && !strict &&
1190:• pers != null && pers.indexOf('@') >= 0 &&
1191:• addr.indexOf('@') < 0 && addr.indexOf('!') < 0) {
1192: String tmp = addr;
1193: addr = pers;
1194: pers = tmp;
1195: }
1196:• if (rfc822 || strict || parseHdr) {
1197:• if (!ignoreErrors)
1198: checkAddress(addr, route_addr, false);
1199: ma = new InternetAddress();
1200: ma.setAddress(addr);
1201:• if (pers != null)
1202: ma.encodedPersonal = pers;
1203: v.add(ma);
1204: } else {
1205: // maybe we passed over more than one space-separated addr
1206: StringTokenizer st = new StringTokenizer(addr);
1207:• while (st.hasMoreTokens()) {
1208: String a = st.nextToken();
1209: checkAddress(a, false, false);
1210: ma = new InternetAddress();
1211: ma.setAddress(a);
1212: v.add(ma);
1213: }
1214: }
1215: }
1216:
1217: InternetAddress[] a = new InternetAddress[v.size()];
1218: v.toArray(a);
1219: return a;
1220: }
1221:
1222: /**
1223: * Validate that this address conforms to the syntax rules of
1224: * RFC 822. The current implementation checks many, but not
1225: * all, syntax rules. Note that even though the syntax of
1226: * the address may be correct, there's no guarantee that a
1227: * mailbox of that name exists.
1228: *
1229: * @throws AddressException if the address isn't valid.
1230: * @since JavaMail 1.3
1231: */
1232: public void validate() throws AddressException {
1233:• if (isGroup())
1234: getGroup(true); // throw away the result
1235: else
1236: checkAddress(getAddress(), true, true);
1237: }
1238:
1239: private static final String specialsNoDotNoAt = "()<>,;:\\\"[]";
1240: private static final String specialsNoDot = specialsNoDotNoAt + "@";
1241:
1242: /**
1243: * Check that the address is a valid "mailbox" per RFC822.
1244: * (We also allow simple names.)
1245: *
1246: * XXX - much more to check
1247: * XXX - doesn't handle domain-literals properly (but no one uses them)
1248: */
1249: private static void checkAddress(String addr,
1250: boolean routeAddr, boolean validate)
1251: throws AddressException {
1252: int i, start = 0;
1253:
1254:• if (addr == null)
1255: throw new AddressException("Address is null");
1256: int len = addr.length();
1257:• if (len == 0)
1258: throw new AddressException("Empty address", addr);
1259:
1260: /*
1261: * routeAddr indicates that the address is allowed
1262: * to have an RFC 822 "route".
1263: */
1264:• if (routeAddr && addr.charAt(0) == '@') {
1265: /*
1266: * Check for a legal "route-addr":
1267: *                [@domain[,@domain ...]:]local@domain
1268: */
1269:• for (start = 0; (i = indexOfAny(addr, ",:", start)) >= 0;
1270: start = i + 1) {
1271:• if (addr.charAt(start) != '@')
1272: throw new AddressException("Illegal route-addr", addr);
1273:• if (addr.charAt(i) == ':') {
1274: // end of route-addr
1275: start = i + 1;
1276: break;
1277: }
1278: }
1279: }
1280:
1281: /*
1282: * The rest should be "local@domain", but we allow simply "local"
1283: * unless called from validate.
1284: *
1285: * local-part must follow RFC 822 - no specials except '.'
1286: * unless quoted.
1287: */
1288:
1289: char c = (char) -1;
1290: char lastc = (char) -1;
1291: boolean inquote = false;
1292:• for (i = start; i < len; i++) {
1293: lastc = c;
1294: c = addr.charAt(i);
1295: // a quoted-pair is only supposed to occur inside a quoted string,
1296: // but some people use it outside so we're more lenient
1297:• if (c == '\\' || lastc == '\\')
1298: continue;
1299:• if (c == '"') {
1300:• if (inquote) {
1301: // peek ahead, next char must be "@"
1302:• if (validate && i + 1 < len && addr.charAt(i + 1) != '@')
1303: throw new AddressException(
1304: "Quote not at end of local address", addr);
1305: inquote = false;
1306: } else {
1307:• if (validate && i != 0)
1308: throw new AddressException(
1309: "Quote not at start of local address", addr);
1310: inquote = true;
1311: }
1312: continue;
1313:• } else if (c == '\r') {
1314: // peek ahead, next char must be LF
1315:• if (i + 1 < len && addr.charAt(i + 1) != '\n')
1316: throw new AddressException(
1317: "Quoted local address contains CR without LF", addr);
1318:• } else if (c == '\n') {
1319: /*
1320: * CRLF followed by whitespace is allowed in a quoted string.
1321: * We allowed naked LF, but ensure LF is always followed by
1322: * whitespace to prevent spoofing the end of the header.
1323: */
1324:• if (i + 1 < len && addr.charAt(i + 1) != ' ' &&
1325:• addr.charAt(i + 1) != '\t')
1326: throw new AddressException(
1327: "Quoted local address contains newline without whitespace",
1328: addr);
1329: }
1330:• if (inquote)
1331: continue;
1332: // dot rules should not be applied to quoted-string
1333:• if (c == '.') {
1334:• if (i == start)
1335: throw new AddressException(
1336: "Local address starts with dot", addr);
1337:• if (lastc == '.')
1338: throw new AddressException(
1339: "Local address contains dot-dot", addr);
1340: }
1341:• if (c == '@') {
1342:• if (i == 0)
1343: throw new AddressException("Missing local name", addr);
1344:• if (lastc == '.')
1345: throw new AddressException(
1346: "Local address ends with dot", addr);
1347: break; // done with local part
1348: }
1349:• if (c <= 040 || c == 0177)
1350: throw new AddressException(
1351: "Local address contains control or whitespace", addr);
1352:• if (specialsNoDot.indexOf(c) >= 0)
1353: throw new AddressException(
1354: "Local address contains illegal character", addr);
1355: }
1356:• if (inquote)
1357: throw new AddressException("Unterminated quote", addr);
1358:
1359: /*
1360: * Done with local part, now check domain.
1361: *
1362: * Note that the MimeMessage class doesn't remember addresses
1363: * as separate objects; it writes them out as headers and then
1364: * parses the headers when the addresses are requested.
1365: * In order to support the case where a "simple" address is used,
1366: * but the address also has a personal name and thus looks like
1367: * it should be a valid RFC822 address when parsed, we only check
1368: * this if we're explicitly called from the validate method.
1369: */
1370:
1371:• if (c != '@') {
1372:• if (validate)
1373: throw new AddressException("Missing final '@domain'", addr);
1374: return;
1375: }
1376:
1377: // check for illegal chars in the domain, but ignore domain literals
1378:
1379: start = i + 1;
1380:• if (start >= len)
1381: throw new AddressException("Missing domain", addr);
1382:
1383:• if (addr.charAt(start) == '.')
1384: throw new AddressException("Domain starts with dot", addr);
1385: boolean inliteral = false;
1386:• for (i = start; i < len; i++) {
1387: c = addr.charAt(i);
1388:• if (c == '[') {
1389:• if (i != start)
1390: throw new AddressException(
1391: "Domain literal not at start of domain", addr);
1392: inliteral = true; // domain literal, don't validate
1393:• } else if (c == ']') {
1394:• if (i != len - 1)
1395: throw new AddressException(
1396: "Domain literal end not at end of domain", addr);
1397: inliteral = false;
1398:• } else if (c <= 040 || c == 0177) {
1399: throw new AddressException(
1400: "Domain contains control or whitespace", addr);
1401: } else {
1402: // RFC 2822 rule
1403: //if (specialsNoDot.indexOf(c) >= 0)
1404: /*
1405: * RFC 1034 rule is more strict
1406: * the full rule is:
1407: *
1408: * <domain> ::= <subdomain> | " "
1409: * <subdomain> ::= <label> | <subdomain> "." <label>
1410: * <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
1411: * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
1412: * <let-dig-hyp> ::= <let-dig> | "-"
1413: * <let-dig> ::= <letter> | <digit>
1414: */
1415:• if (!inliteral) {
1416:• if (!(Character.isLetterOrDigit(c) || c == '-' || c == '.'))
1417: throw new AddressException(
1418: "Domain contains illegal character", addr);
1419:• if (c == '.' && lastc == '.')
1420: throw new AddressException(
1421: "Domain contains dot-dot", addr);
1422: }
1423: }
1424: lastc = c;
1425: }
1426:• if (lastc == '.')
1427: throw new AddressException("Domain ends with dot", addr);
1428: }
1429:
1430: /**
1431: * Is this a "simple" address? Simple addresses don't contain quotes
1432: * or any RFC822 special characters other than '@' and '.'.
1433: */
1434: private boolean isSimple() {
1435:• return address == null || indexOfAny(address, specialsNoDotNoAt) < 0;
1436: }
1437:
1438: /**
1439: * Indicates whether this address is an RFC 822 group address.
1440: * Note that a group address is different than the mailing
1441: * list addresses supported by most mail servers. Group addresses
1442: * are rarely used; see RFC 822 for details.
1443: *
1444: * @return true if this address represents a group
1445: * @since JavaMail 1.3
1446: */
1447: public boolean isGroup() {
1448: // quick and dirty check
1449:• return address != null &&
1450:• address.endsWith(";") && address.indexOf(':') > 0;
1451: }
1452:
1453: /**
1454: * Return the members of a group address. A group may have zero,
1455: * one, or more members. If this address is not a group, null
1456: * is returned. The <code>strict</code> parameter controls whether
1457: * the group list is parsed using strict RFC 822 rules or not.
1458: * The parsing is done using the <code>parseHeader</code> method.
1459: *
1460: * @param strict use strict RFC 822 rules?
1461: * @return array of InternetAddress objects, or null
1462: * @throws AddressException if the group list can't be parsed
1463: * @since JavaMail 1.3
1464: */
1465: public InternetAddress[] getGroup(boolean strict) throws AddressException {
1466: String addr = getAddress();
1467:• if (addr == null)
1468: return null;
1469: // groups are of the form "name:addr,addr,...;"
1470:• if (!addr.endsWith(";"))
1471: return null;
1472: int ix = addr.indexOf(':');
1473:• if (ix < 0)
1474: return null;
1475: // extract the list
1476: String list = addr.substring(ix + 1, addr.length() - 1);
1477: // parse it and return the individual addresses
1478: return InternetAddress.parseHeader(list, strict);
1479: }
1480:
1481: /**
1482: * Return the first index of any of the characters in "any" in "s",
1483: * or -1 if none are found.
1484: *
1485: * This should be a method on String.
1486: */
1487: private static int indexOfAny(String s, String any) {
1488: return indexOfAny(s, any, 0);
1489: }
1490:
1491: private static int indexOfAny(String s, String any, int start) {
1492: try {
1493: int len = s.length();
1494:• for (int i = start; i < len; i++) {
1495:• if (any.indexOf(s.charAt(i)) >= 0)
1496: return i;
1497: }
1498: return -1;
1499: } catch (StringIndexOutOfBoundsException e) {
1500: return -1;
1501: }
1502: }
1503:
1504: /*
1505: public static void main(String argv[]) throws Exception {
1506:         for (int i = 0; i < argv.length; i++) {
1507:          InternetAddress[] a = InternetAddress.parse(argv[i]);
1508:          for (int j = 0; j < a.length; j++) {
1509:                 System.out.println("arg " + i + " address " + j + ": " + a[j]);
1510:                 System.out.println("\tAddress: " + a[j].getAddress() +
1511:                                  "\tPersonal: " + a[j].getPersonal());
1512:          }
1513:          if (a.length > 1) {
1514:                 System.out.println("address 0 hash code: " + a[0].hashCode());
1515:                 System.out.println("address 1 hash code: " + a[1].hashCode());
1516:                 if (a[0].hashCode() == a[1].hashCode())
1517:                  System.out.println("success, hashcodes equal");
1518:                 else
1519:                  System.out.println("fail, hashcodes not equal");
1520:                 if (a[0].equals(a[1]))
1521:                  System.out.println("success, addresses equal");
1522:                 else
1523:                  System.out.println("fail, addresses not equal");
1524:                 if (a[1].equals(a[0]))
1525:                  System.out.println("success, addresses equal");
1526:                 else
1527:                  System.out.println("fail, addresses not equal");
1528:          }
1529:         }
1530: }
1531: */
1532: }