PageRenderTime 53ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/mail/src/main/java/javax/mail/internet/InternetAddress.java

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