1. /*
  2. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  3. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4. */
  5. package javax.mail.internet;
  6. import java.io.UnsupportedEncodingException;
  7. import java.net.InetAddress;
  8. import java.net.UnknownHostException;
  9. import java.util.Vector;
  10. import java.util.StringTokenizer;
  11. import javax.mail.*;
  12. /**
  13. * This class models an RFC822 address.
  14. *
  15. * @author Bill Shannon
  16. * @author John Mani
  17. */
  18. public class InternetAddress extends Address implements Cloneable {
  19. protected String address; // email address
  20. /**
  21. * The personal name.
  22. */
  23. protected String personal;
  24. /**
  25. * The RFC 2047 encoded version of the personal name. <p>
  26. *
  27. * This field and the <code>personal</code> field track each
  28. * other, so if a subclass sets one of these fields directly, it
  29. * should set the other to <code>null</code>, so that it is
  30. * suitably recomputed.
  31. */
  32. protected String encodedPersonal;
  33. /**
  34. * Default constructor.
  35. */
  36. public InternetAddress() { }
  37. /**
  38. * Constructor. <p>
  39. *
  40. * Parse the given string and create an InternetAddress.
  41. *
  42. * @param address the address in RFC822 format
  43. * @exception AddressException if the parse failed
  44. */
  45. public InternetAddress(String address) throws AddressException {
  46. // use our address parsing utility routine to parse the string
  47. InternetAddress a[] = parse(address, true);
  48. // if we got back anything other than a single address, it's an error
  49. if (a.length != 1)
  50. throw new AddressException("Illegal address", address);
  51. /*
  52. * Now copy the contents of the single address we parsed
  53. * into the current object, which will be returned from the
  54. * constructor.
  55. * XXX - this sure is a round-about way of getting this done.
  56. */
  57. this.address = a[0].address;
  58. this.personal = a[0].personal;
  59. this.encodedPersonal = a[0].encodedPersonal;
  60. }
  61. /**
  62. * Parse the given string and create an InternetAddress.
  63. * If <code>strict</code> is false, simple addresses with
  64. * no host or domain are allowed.
  65. *
  66. * @param address the address in RFC822 format
  67. * @param strict enforce RFC822 syntax
  68. * @exception AddressException if the parse failed
  69. */
  70. // XXX - should be public in JavaMail 1.2
  71. private InternetAddress(String address, boolean strict)
  72. throws AddressException {
  73. this(address);
  74. if (strict)
  75. checkAddress(this.address, true, true);
  76. }
  77. /**
  78. * Construct an InternetAddress given the address and personal name.
  79. * The address is assumed to be a syntactically valid RFC822 address.
  80. *
  81. * @param address the address in RFC822 format
  82. * @param personal the personal name
  83. */
  84. public InternetAddress(String address, String personal)
  85. throws UnsupportedEncodingException {
  86. this(address, personal, null);
  87. }
  88. /**
  89. * Construct an InternetAddress given the address and personal name.
  90. * The address is assumed to be a syntactically valid RFC822 address.
  91. *
  92. * @param address the address in RFC822 format
  93. * @param personal the personal name
  94. * @param charset the charset for the name
  95. */
  96. public InternetAddress(String address, String personal, String charset)
  97. throws UnsupportedEncodingException {
  98. this.address = address;
  99. setPersonal(personal, charset);
  100. }
  101. /**
  102. * Return a copy of this InternetAddress object.
  103. * @since JavaMail 1.2
  104. */
  105. public Object clone() {
  106. InternetAddress a = null;
  107. try {
  108. a = (InternetAddress)super.clone();
  109. } catch (CloneNotSupportedException e) {} // Won't happen
  110. return a;
  111. }
  112. /**
  113. * Return the type of this address. The type of an InternetAddress
  114. * is "rfc822".
  115. */
  116. public String getType() {
  117. return "rfc822";
  118. }
  119. /**
  120. * Set the email address.
  121. *
  122. * @param address email address
  123. */
  124. public void setAddress(String address) {
  125. this.address = address;
  126. }
  127. /**
  128. * Set the personal name. If the name contains non US-ASCII
  129. * characters, then the name will be encoded using the specified
  130. * charset as per RFC 2047. If the name contains only US-ASCII
  131. * characters, no encoding is done and the name is used as is. <p>
  132. *
  133. * @param name personal name
  134. * @param charset charset to be used to encode the name as
  135. * per RFC 2047.
  136. * @see #setPersonal(String)
  137. * @exception UnsupportedEncodingException if the charset encoding
  138. * fails.
  139. */
  140. public void setPersonal(String name, String charset)
  141. throws UnsupportedEncodingException {
  142. personal = name;
  143. if (name != null)
  144. encodedPersonal = MimeUtility.encodeWord(name, charset, null);
  145. else
  146. encodedPersonal = null;
  147. }
  148. /**
  149. * Set the personal name. If the name contains non US-ASCII
  150. * characters, then the name will be encoded using the platform's
  151. * default charset. If the name contains only US-ASCII characters,
  152. * no encoding is done and the name is used as is. <p>
  153. *
  154. * @param name personal name
  155. * @see #setPersonal(String name, String charset)
  156. * @exception UnsupportedEncodingException if the charset encoding
  157. * fails.
  158. */
  159. public void setPersonal(String name)
  160. throws UnsupportedEncodingException {
  161. personal = name;
  162. if (name != null)
  163. encodedPersonal = MimeUtility.encodeWord(name);
  164. else
  165. encodedPersonal = null;
  166. }
  167. /**
  168. * Get the email address.
  169. * @return email address
  170. */
  171. public String getAddress() {
  172. return address;
  173. }
  174. /**
  175. * Get the personal name. If the name is encoded as per RFC 2047,
  176. * it is decoded and converted into Unicode. If the decoding or
  177. * convertion fails, the raw data is returned as is.
  178. *
  179. * @return personal name
  180. */
  181. public String getPersonal() {
  182. if (personal != null)
  183. return personal;
  184. if (encodedPersonal != null) {
  185. try {
  186. personal = MimeUtility.decodeText(encodedPersonal);
  187. return personal;
  188. } catch (Exception ex) {
  189. // 1. ParseException: either its an unencoded string or
  190. // it can't be parsed
  191. // 2. UnsupportedEncodingException: can't decode it.
  192. return encodedPersonal;
  193. }
  194. }
  195. // No personal or encodedPersonal, return null
  196. return null;
  197. }
  198. /**
  199. * Convert this address into a RFC 822 / RFC 2047 encoded address.
  200. * The resulting string contains only US-ASCII characters, and
  201. * hence is mail-safe.
  202. *
  203. * @return possibly encoded address string
  204. */
  205. public String toString() {
  206. if (encodedPersonal == null && personal != null)
  207. try {
  208. encodedPersonal = MimeUtility.encodeWord(personal);
  209. } catch (UnsupportedEncodingException ex) { }
  210. if (encodedPersonal != null)
  211. return quotePhrase(encodedPersonal) + " <" + address + ">";
  212. else if (isGroup() || isSimple())
  213. return address;
  214. else
  215. return "<" + address + ">";
  216. }
  217. /**
  218. * Returns a properly formatted address (RFC 822 syntax) of
  219. * Unicode characters.
  220. *
  221. * @return Unicode address string
  222. * @since JavaMail 1.2
  223. */
  224. public String toUnicodeString() {
  225. if (getPersonal() != null)
  226. return quotePhrase(personal) + " <" + address + ">";
  227. else if (isGroup() || isSimple())
  228. return address;
  229. else
  230. return "<" + address + ">";
  231. }
  232. /*
  233. * quotePhrase() quotes the words within a RFC822 phrase.
  234. *
  235. * This is tricky, since a phrase is defined as 1 or more
  236. * RFC822 words, separated by LWSP. Now, a word that contains
  237. * LWSP is supposed to be quoted, and this is exactly what the
  238. * MimeUtility.quote() method does. However, when dealing with
  239. * a phrase, any LWSP encountered can be construed to be the
  240. * separator between words, and not part of the words themselves.
  241. * To deal with this funkiness, we have the below variant of
  242. * MimeUtility.quote(), which essentially ignores LWSP when
  243. * deciding whether to quote a word.
  244. *
  245. * It aint pretty, but it gets the job done :)
  246. */
  247. private static final String rfc822phrase =
  248. HeaderTokenizer.RFC822.replace(' ', '\0').replace('\t', '\0');
  249. private static String quotePhrase(String phrase) {
  250. int len = phrase.length();
  251. boolean needQuoting = false;
  252. for (int i = 0; i < len; i++) {
  253. char c = phrase.charAt(i);
  254. if (c == '"' || c == '\\') {
  255. // need to escape them and then quote the whole string
  256. StringBuffer sb = new StringBuffer(len + 3);
  257. sb.append('"');
  258. for (int j = 0; j < len; j++) {
  259. char cc = phrase.charAt(j);
  260. if (cc == '"' || cc == '\\')
  261. // Escape the character
  262. sb.append('\\');
  263. sb.append(cc);
  264. }
  265. sb.append('"');
  266. return sb.toString();
  267. } else if ((c < 040 && c != '\r' && c != '\n' && c != '\t') ||
  268. c >= 0177 || rfc822phrase.indexOf(c) >= 0)
  269. // These characters cause the string to be quoted
  270. needQuoting = true;
  271. }
  272. if (needQuoting) {
  273. StringBuffer sb = new StringBuffer(len + 2);
  274. sb.append('"').append(phrase).append('"');
  275. return sb.toString();
  276. } else
  277. return phrase;
  278. }
  279. private static String unquote(String s) {
  280. if (s.startsWith("\"") && s.endsWith("\"")) {
  281. s = s.substring(1, s.length() - 1);
  282. // check for any escaped characters
  283. if (s.indexOf('\\') >= 0) {
  284. StringBuffer sb = new StringBuffer(s.length()); // approx
  285. for (int i = 0; i < s.length(); i++) {
  286. char c = s.charAt(i);
  287. if (c == '\\' && i < s.length() - 1)
  288. c = s.charAt(++i);
  289. sb.append(c);
  290. }
  291. s = sb.toString();
  292. }
  293. }
  294. return s;
  295. }
  296. /**
  297. * The equality operator.
  298. */
  299. public boolean equals(Object a) {
  300. if (!(a instanceof InternetAddress))
  301. return false;
  302. String s = ((InternetAddress)a).getAddress();
  303. if (s == address)
  304. return true;
  305. if (address != null && address.equalsIgnoreCase(s))
  306. return true;
  307. return false;
  308. }
  309. /**
  310. * Compute a hash code for the address.
  311. */
  312. public int hashCode() {
  313. if (address == null)
  314. return 0;
  315. else
  316. return address.toLowerCase().hashCode();
  317. }
  318. /**
  319. * Convert the given array of InternetAddress objects into
  320. * a comma separated sequence of address strings. The
  321. * resulting string contains only US-ASCII characters, and
  322. * hence is mail-safe. <p>
  323. *
  324. * @param addresses array of InternetAddress objects
  325. * @exception ClassCastException, if any address object in the
  326. * given array is not an InternetAddress objects. Note
  327. * that this is a RuntimeException.
  328. * @return comma separated address strings
  329. */
  330. public static String toString(Address[] addresses) {
  331. return toString(addresses, 0);
  332. }
  333. /**
  334. * Convert the given array of InternetAddress objects into
  335. * a comma separated sequence of address strings. The
  336. * resulting string contains only US-ASCII characters, and
  337. * hence is mail-safe. <p>
  338. *
  339. * The 'used' parameter specifies the number of character positions
  340. * already taken up in the field into which the resulting address
  341. * sequence string is to be inserted. Its used to determine the
  342. * line-break positions in the resulting address sequence string.
  343. *
  344. * @param addresses array of InternetAddress objects
  345. * @param used number of character positions already used, in
  346. * the field into which the address string is to
  347. * be inserted.
  348. * @exception ClassCastException, if any address object in the
  349. * given array is not an InternetAddress objects. Note
  350. * that this is a RuntimeException.
  351. * @return comma separated address strings
  352. */
  353. public static String toString(Address[] addresses, int used) {
  354. if (addresses == null || addresses.length == 0)
  355. return null;
  356. StringBuffer sb = new StringBuffer();
  357. for (int i = 0; i < addresses.length; i++) {
  358. if (i != 0) { // need to append comma
  359. sb.append(", ");
  360. used += 2;
  361. }
  362. String s = addresses[i].toString();
  363. int len = lengthOfFirstSegment(s); // length till CRLF
  364. if (used + len > 76) { // overflows ...
  365. sb.append("\r\n\t"); // .. start new continuation line
  366. used = 8; // account for the starting <tab> char
  367. }
  368. sb.append(s);
  369. used = lengthOfLastSegment(s, used);
  370. }
  371. return sb.toString();
  372. }
  373. /* Return the length of the first segment within this string.
  374. * If no segments exist, the length of the whole line is returned.
  375. */
  376. private static int lengthOfFirstSegment(String s) {
  377. int pos;
  378. if ((pos = s.indexOf("\r\n")) != -1)
  379. return pos;
  380. else
  381. return s.length();
  382. }
  383. /*
  384. * Return the length of the last segment within this string.
  385. * If no segments exist, the length of the whole line plus
  386. * <code>used</code> is returned.
  387. */
  388. private static int lengthOfLastSegment(String s, int used) {
  389. int pos;
  390. if ((pos = s.lastIndexOf("\r\n")) != -1)
  391. return s.length() - pos - 2;
  392. else
  393. return s.length() + used;
  394. }
  395. /**
  396. * Return an InternetAddress object representing the current user.
  397. * The entire email address may be specified in the "mail.from"
  398. * property. If not set, the "mail.user" and "mail.host" properties
  399. * are tried. If those are not set, the "user.name" property and
  400. * <code>InetAddress.getLocalHost</code> method are tried.
  401. * Security exceptions that may occur while accessing this information
  402. * are ignored. If it is not possible to determine an email address,
  403. * null is returned.
  404. *
  405. * @param session Session object used for property lookup
  406. * @return current user's email address
  407. */
  408. public static InternetAddress getLocalAddress(Session session) {
  409. String user=null, host=null, address=null;
  410. try {
  411. if (session == null) {
  412. user = System.getProperty("user.name");
  413. host = InetAddress.getLocalHost().getHostName();
  414. } else {
  415. address = session.getProperty("mail.from");
  416. if (address == null) {
  417. user = session.getProperty("mail.user");
  418. if (user == null || user.length() == 0)
  419. user = session.getProperty("user.name");
  420. if (user == null || user.length() == 0)
  421. user = System.getProperty("user.name");
  422. host = session.getProperty("mail.host");
  423. if (host == null || host.length() == 0) {
  424. InetAddress me = InetAddress.getLocalHost();
  425. if (me != null)
  426. host = me.getHostName();
  427. }
  428. }
  429. }
  430. if (address == null && user != null && user.length() != 0 &&
  431. host != null && host.length() != 0)
  432. address = user + "@" + host;
  433. if (address != null)
  434. return new InternetAddress(address);
  435. } catch (SecurityException sex) { // ignore it
  436. } catch (AddressException ex) { // ignore it
  437. } catch (UnknownHostException ex) { } // ignore it
  438. return null;
  439. }
  440. /**
  441. * Parse the given comma separated sequence of addresses into
  442. * InternetAddress objects. Addresses must follow RFC822 syntax.
  443. *
  444. * @param addresslist comma separated address strings
  445. * @return array of InternetAddress objects
  446. * @exception AddressException if the parse failed
  447. */
  448. public static InternetAddress[] parse(String addresslist)
  449. throws AddressException {
  450. return parse(addresslist, true);
  451. }
  452. /**
  453. * Parse the given sequence of addresses into InternetAddress
  454. * objects. If <code>strict</code> is false, simple email addresses
  455. * separated by spaces are also allowed. If <code>strict</code> is
  456. * true, many (but not all) of the RFC822 syntax rules are enforced.
  457. * In particular, even if <code>strict</code> is true, addresses
  458. * composed of simple names (with no "@domain" part) are allowed.
  459. * Such "illegal" addresses are not uncommon in real messages. <p>
  460. *
  461. * Non-strict parsing is typically used when parsing a list of
  462. * mail addresses entered by a human. Strict parsing is typically
  463. * used when parsing address headers in mail messages.
  464. *
  465. * @param addresslist comma separated address strings
  466. * @param strict enforce RFC822 syntax
  467. * @return array of InternetAddress objects
  468. * @exception AddressException if the parse failed
  469. */
  470. /*
  471. * RFC822 Address parser.
  472. *
  473. * XXX - This is complex enough that it ought to be a real parser,
  474. * not this ad-hoc mess, and because of that, this is not perfect.
  475. *
  476. * XXX - Deal with encoded Headers too.
  477. */
  478. public static InternetAddress[] parse(String s, boolean strict)
  479. throws AddressException {
  480. int start, end, index, nesting;
  481. int start_personal = -1, end_personal = -1;
  482. int length = s.length();
  483. boolean in_group = false; // we're processing a group term
  484. boolean route_addr = false; // address came from route-addr term
  485. boolean rfc822 = false; // looks like an RFC822 address
  486. char c;
  487. Vector v = new Vector();
  488. InternetAddress ma;
  489. for (start = end = -1, index = 0; index < length; index++) {
  490. c = s.charAt(index);
  491. switch (c) {
  492. case '(': // We are parsing a Comment. Ignore everything inside.
  493. // XXX - comment fields should be parsed as whitespace,
  494. // more than one allowed per address
  495. rfc822 = true;
  496. if (start >= 0 && end == -1)
  497. end = index;
  498. if (start_personal == -1)
  499. start_personal = index + 1;
  500. for (index++, nesting = 1; index < length && nesting > 0;
  501. index++) {
  502. c = s.charAt(index);
  503. switch (c) {
  504. case '\\':
  505. index++; // skip both '\' and the escaped char
  506. break;
  507. case '(':
  508. nesting++;
  509. break;
  510. case ')':
  511. nesting--;
  512. break;
  513. default:
  514. break;
  515. }
  516. }
  517. if (nesting > 0)
  518. throw new AddressException("Missing ')'", s, index);
  519. index--; // point to closing paren
  520. if (end_personal == -1)
  521. end_personal = index;
  522. break;
  523. case ')':
  524. throw new AddressException("Missing '('", s, index);
  525. case '<':
  526. rfc822 = true;
  527. if (route_addr)
  528. throw new AddressException("Extra route-addr", s, index);
  529. if (!in_group) {
  530. start_personal = start;
  531. if (start_personal >= 0)
  532. end_personal = index;
  533. start = index + 1;
  534. }
  535. boolean inquote = false;
  536. outf:
  537. for (index++; index < length; index++) {
  538. c = s.charAt(index);
  539. switch (c) {
  540. case '\\': // XXX - is this needed?
  541. index++; // skip both '\' and the escaped char
  542. break;
  543. case '"':
  544. inquote = !inquote;
  545. break;
  546. case '>':
  547. if (inquote)
  548. continue;
  549. break outf; // out of for loop
  550. default:
  551. break;
  552. }
  553. }
  554. if (index >= length) {
  555. if (inquote)
  556. throw new AddressException("Missing '\"'", s, index);
  557. else
  558. throw new AddressException("Missing '>'", s, index);
  559. }
  560. route_addr = true;
  561. end = index;
  562. break;
  563. case '>':
  564. throw new AddressException("Missing '<'", s, index);
  565. case '"': // parse quoted string
  566. rfc822 = true;
  567. if (start == -1)
  568. start = index;
  569. outq:
  570. for (index++; index < length; index++) {
  571. c = s.charAt(index);
  572. switch (c) {
  573. case '\\':
  574. index++; // skip both '\' and the escaped char
  575. break;
  576. case '"':
  577. break outq; // out of for loop
  578. default:
  579. break;
  580. }
  581. }
  582. if (index >= length)
  583. throw new AddressException("Missing '\"'", s, index);
  584. break;
  585. case '[': // a domain-literal, probably
  586. rfc822 = true;
  587. outb:
  588. for (index++; index < length; index++) {
  589. c = s.charAt(index);
  590. switch (c) {
  591. case '\\':
  592. index++; // skip both '\' and the escaped char
  593. break;
  594. case ']':
  595. break outb; // out of for loop
  596. default:
  597. break;
  598. }
  599. }
  600. if (index >= length)
  601. throw new AddressException("Missing ']'", s, index);
  602. break;
  603. case ',': // end of an address, probably
  604. if (start == -1) {
  605. route_addr = false;
  606. rfc822 = false;
  607. start = end = -1;
  608. break; // nope, nothing there
  609. }
  610. if (in_group)
  611. break;
  612. // got a token, add this to our InternetAddress vector
  613. if (end == -1)
  614. end = index;
  615. String addr = s.substring(start, end).trim();
  616. if (rfc822 || strict) {
  617. checkAddress(addr, route_addr, strict);
  618. ma = new InternetAddress();
  619. ma.setAddress(addr);
  620. if (start_personal >= 0) {
  621. ma.encodedPersonal = unquote(
  622. s.substring(start_personal, end_personal).trim());
  623. start_personal = end_personal = -1;
  624. }
  625. v.addElement(ma);
  626. } else {
  627. // maybe we passed over more than one space-separated addr
  628. StringTokenizer st = new StringTokenizer(addr);
  629. while (st.hasMoreTokens()) {
  630. String a = st.nextToken();
  631. checkAddress(a, false, strict);
  632. ma = new InternetAddress();
  633. ma.setAddress(a);
  634. v.addElement(ma);
  635. }
  636. }
  637. route_addr = false;
  638. rfc822 = false;
  639. start = end = -1;
  640. break;
  641. case ':':
  642. rfc822 = true;
  643. if (in_group)
  644. throw new AddressException("Nested group", s, index);
  645. in_group = true;
  646. break;
  647. case ';':
  648. if (!in_group)
  649. throw new AddressException(
  650. "Illegal semicolon, not in group", s, index);
  651. in_group = false;
  652. ma = new InternetAddress();
  653. end = index + 1;
  654. ma.setAddress(s.substring(start, end).trim());
  655. v.addElement(ma);
  656. route_addr = false;
  657. start = end = -1;
  658. break;
  659. // Ignore whitespace
  660. case ' ':
  661. case '\t':
  662. case '\r':
  663. case '\n':
  664. break;
  665. default:
  666. if (start == -1)
  667. start = index;
  668. break;
  669. }
  670. }
  671. if (start >= 0) {
  672. /*
  673. * The last token, add this to our InternetAddress vector.
  674. * Note that this block of code should be identical to the
  675. * block above for "case ','".
  676. */
  677. if (end == -1)
  678. end = index;
  679. String addr = s.substring(start, end).trim();
  680. if (rfc822 || strict) {
  681. checkAddress(addr, route_addr, strict);
  682. ma = new InternetAddress();
  683. ma.setAddress(addr);
  684. if (start_personal >= 0) {
  685. ma.encodedPersonal = unquote(
  686. s.substring(start_personal, end_personal).trim());
  687. }
  688. v.addElement(ma);
  689. } else {
  690. // maybe we passed over more than one space-separated addr
  691. StringTokenizer st = new StringTokenizer(addr);
  692. while (st.hasMoreTokens()) {
  693. String a = st.nextToken();
  694. checkAddress(a, false, strict);
  695. ma = new InternetAddress();
  696. ma.setAddress(a);
  697. v.addElement(ma);
  698. }
  699. }
  700. }
  701. InternetAddress[] a = new InternetAddress[v.size()];
  702. v.copyInto(a);
  703. return a;
  704. }
  705. private static final String specialsNoDotNoAt = "()<>,;:\\\"[]";
  706. private static final String specialsNoDot = specialsNoDotNoAt + "@";
  707. /**
  708. * Check that the address is a valid "mailbox" per RFC822.
  709. * (We also allow simple names.)
  710. *
  711. * XXX - much more to check
  712. * XXX - doesn't handle domain-literals properly (but no one uses them)
  713. */
  714. private static void checkAddress(String addr,
  715. boolean routeAddr, boolean strict)
  716. throws AddressException {
  717. int i, start = 0;
  718. if (addr.indexOf('"') >= 0)
  719. return; // quote in address, too hard to check
  720. if (!strict || routeAddr) {
  721. /*
  722. * Check for a legal "route-addr":
  723. * [@domain[,@domain ...]:]local@domain
  724. */
  725. for (start = 0; (i = indexOfAny(addr, ",:", start)) >= 0;
  726. start = i+1) {
  727. if (addr.charAt(start) != '@')
  728. throw new AddressException("Illegal route-addr", addr);
  729. if (addr.charAt(i) == ':') {
  730. // end of route-addr
  731. start = i + 1;
  732. break;
  733. }
  734. }
  735. }
  736. /*
  737. * The rest should be "local@domain", but we allow simply "local"
  738. * if this is not a route-addr.
  739. */
  740. String local;
  741. String domain;
  742. if ((i = addr.indexOf('@', start)) >= 0) {
  743. if (i == start)
  744. throw new AddressException("Missing local name", addr);
  745. if (i == addr.length() - 1)
  746. throw new AddressException("Missing domain", addr);
  747. local = addr.substring(start, i);
  748. domain = addr.substring(i + 1);
  749. } else {
  750. /*
  751. * No '@' so it's not *really* an RFC822 address, but still
  752. * we allow some simple forms.
  753. */
  754. local = addr;
  755. domain = null;
  756. /*
  757. * Remove this check for now. It causes problems when an
  758. * InternetAddress is created with a simple name, and a
  759. * personal name is later set.
  760. *
  761. if (strict && routeAddr)
  762. throw new AddressException("Missing final '@domain'", addr);
  763. */
  764. }
  765. // there better not be any whitespace in it
  766. if (indexOfAny(addr, " \t\n\r") >= 0)
  767. throw new AddressException("Illegal whitespace in address", addr);
  768. // local-part must follow RFC822, no specials except '.'
  769. if (indexOfAny(local, specialsNoDot) >= 0)
  770. throw new AddressException("Illegal character in local name", addr);
  771. // check for illegal chars in the domain, but ignore domain literals
  772. if (domain != null && domain.indexOf('[') < 0) {
  773. if (indexOfAny(domain, specialsNoDot) >= 0)
  774. throw new AddressException("Illegal character in domain", addr);
  775. }
  776. }
  777. /**
  778. * Is this a "simple" address? Simple addresses don't contain quotes
  779. * or any RFC822 special characters other than '@' and '.'.
  780. */
  781. private boolean isSimple() {
  782. return indexOfAny(address, specialsNoDotNoAt) < 0;
  783. }
  784. /**
  785. * Is this a group address (group:addr1,addr2,...;)?
  786. */
  787. private boolean isGroup() {
  788. // quick and dirty check
  789. return address.endsWith(";") && address.indexOf(':') > 0;
  790. }
  791. /**
  792. * Return the first index of any of the characters in "any" in "s",
  793. * or -1 if none are found.
  794. *
  795. * This should be a method on String.
  796. */
  797. private static int indexOfAny(String s, String any) {
  798. return indexOfAny(s, any, 0);
  799. }
  800. private static int indexOfAny(String s, String any, int start) {
  801. try {
  802. int len = s.length();
  803. for (int i = start; i < len; i++) {
  804. if (any.indexOf(s.charAt(i)) >= 0)
  805. return i;
  806. }
  807. return -1;
  808. } catch (StringIndexOutOfBoundsException e) {
  809. return -1;
  810. }
  811. }
  812. /*
  813. public static void main(String argv[]) throws Exception {
  814. for (int i = 0; i < argv.length; i++) {
  815. InternetAddress[] a = InternetAddress.parse(argv[i]);
  816. for (int j = 0; j < a.length; j++) {
  817. System.out.println("arg " + i + " address " + j + ": " + a[j]);
  818. System.out.println("\tAddress: " + a[j].getAddress() +
  819. "\tPersonal: " + a[j].getPersonal());
  820. }
  821. if (a.length > 1) {
  822. System.out.println("address 0 hash code: " + a[0].hashCode());
  823. System.out.println("address 1 hash code: " + a[1].hashCode());
  824. if (a[0].hashCode() == a[1].hashCode())
  825. System.out.println("success, hashcodes equal");
  826. else
  827. System.out.println("fail, hashcodes not equal");
  828. if (a[0].equals(a[1]))
  829. System.out.println("success, addresses equal");
  830. else
  831. System.out.println("fail, addresses not equal");
  832. if (a[1].equals(a[0]))
  833. System.out.println("success, addresses equal");
  834. else
  835. System.out.println("fail, addresses not equal");
  836. }
  837. }
  838. }
  839. */
  840. }