/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail.internet;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Locale;
import java.nio.charset.StandardCharsets;
import javax.mail.*;
import com.sun.mail.util.PropUtil;

This class represents an Internet email address using the syntax of RFC822. Typical address syntax is of the form "user@host.domain" or "Personal Name <user@host.domain>".
Author:Bill Shannon, John Mani
/** * This class represents an Internet email address using the syntax * of <a href="http://www.ietf.org/rfc/rfc822.txt" target="_top">RFC822</a>. * Typical address syntax is of the form "user@host.domain" or * "Personal Name &lt;user@host.domain&gt;". * * @author Bill Shannon * @author John Mani */
public class InternetAddress extends Address implements Cloneable { protected String address; // email address
The personal name.
/** * The personal name. */
protected String personal;
The RFC 2047 encoded version of the personal name.

This field and the personal field track each other, so if a subclass sets one of these fields directly, it should set the other to null, so that it is suitably recomputed.

/** * The RFC 2047 encoded version of the personal name. <p> * * This field and the <code>personal</code> field track each * other, so if a subclass sets one of these fields directly, it * should set the other to <code>null</code>, so that it is * suitably recomputed. */
protected String encodedPersonal; private static final long serialVersionUID = -7507595530758302903L; private static final boolean ignoreBogusGroupName = PropUtil.getBooleanSystemProperty( "mail.mime.address.ignorebogusgroupname", true); private static final boolean useCanonicalHostName = PropUtil.getBooleanSystemProperty( "mail.mime.address.usecanonicalhostname", true); private static final boolean allowUtf8 = PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", false);
Default constructor.
/** * Default constructor. */
public InternetAddress() { }
Constructor.

Parse the given string and create an InternetAddress. See the parse method for details of the parsing. The address is parsed using "strict" parsing. This constructor does not perform the additional syntax checks that the InternetAddress(String address, boolean strict) constructor does when strict is true. This constructor is equivalent to InternetAddress(address, false).

Params:
  • address – the address in RFC822 format
Throws:
/** * Constructor. <p> * * Parse the given string and create an InternetAddress. * See the <code>parse</code> method for details of the parsing. * The address is parsed using "strict" parsing. * This constructor does <b>not</b> perform the additional * syntax checks that the * <code>InternetAddress(String address, boolean strict)</code> * constructor does when <code>strict</code> is <code>true</code>. * This constructor is equivalent to * <code>InternetAddress(address, false)</code>. * * @param address the address in RFC822 format * @exception AddressException if the parse failed */
public InternetAddress(String address) throws AddressException { // use our address parsing utility routine to parse the string InternetAddress a[] = parse(address, true); // if we got back anything other than a single address, it's an error if (a.length != 1) throw new AddressException("Illegal address", address); /* * Now copy the contents of the single address we parsed * into the current object, which will be returned from the * constructor. * XXX - this sure is a round-about way of getting this done. */ this.address = a[0].address; this.personal = a[0].personal; this.encodedPersonal = a[0].encodedPersonal; }
Parse the given string and create an InternetAddress. If strict is false, the detailed syntax of the address isn't checked.
Params:
  • address – the address in RFC822 format
  • strict – enforce RFC822 syntax
Throws:
Since: JavaMail 1.3
/** * Parse the given string and create an InternetAddress. * If <code>strict</code> is false, the detailed syntax of the * address isn't checked. * * @param address the address in RFC822 format * @param strict enforce RFC822 syntax * @exception AddressException if the parse failed * @since JavaMail 1.3 */
public InternetAddress(String address, boolean strict) throws AddressException { this(address); if (strict) { if (isGroup()) getGroup(true); // throw away the result else checkAddress(this.address, true, true); } }
Construct an InternetAddress given the address and personal name. The address is assumed to be a syntactically valid RFC822 address.
Params:
  • address – the address in RFC822 format
  • personal – the personal name
Throws:
/** * Construct an InternetAddress given the address and personal name. * The address is assumed to be a syntactically valid RFC822 address. * * @param address the address in RFC822 format * @param personal the personal name * @exception UnsupportedEncodingException if the personal name * can't be encoded in the given charset */
public InternetAddress(String address, String personal) throws UnsupportedEncodingException { this(address, personal, null); }
Construct an InternetAddress given the address and personal name. The address is assumed to be a syntactically valid RFC822 address.
Params:
  • address – the address in RFC822 format
  • personal – the personal name
  • charset – the MIME charset for the name
Throws:
/** * Construct an InternetAddress given the address and personal name. * The address is assumed to be a syntactically valid RFC822 address. * * @param address the address in RFC822 format * @param personal the personal name * @param charset the MIME charset for the name * @exception UnsupportedEncodingException if the personal name * can't be encoded in the given charset */
public InternetAddress(String address, String personal, String charset) throws UnsupportedEncodingException { this.address = address; setPersonal(personal, charset); }
Return a copy of this InternetAddress object.
Since: JavaMail 1.2
/** * Return a copy of this InternetAddress object. * @since JavaMail 1.2 */
@Override public Object clone() { InternetAddress a = null; try { a = (InternetAddress)super.clone(); } catch (CloneNotSupportedException e) {} // Won't happen return a; }
Return the type of this address. The type of an InternetAddress is "rfc822".
/** * Return the type of this address. The type of an InternetAddress * is "rfc822". */
@Override public String getType() { return "rfc822"; }
Set the email address.
Params:
  • address – email address
/** * Set the email address. * * @param address email address */
public void setAddress(String address) { this.address = address; }
Set the personal name. If the name contains non US-ASCII characters, then the name will be encoded using the specified charset as per RFC 2047. If the name contains only US-ASCII characters, no encoding is done and the name is used as is.

Params:
  • name – personal name
  • charset – MIME charset to be used to encode the name as per RFC 2047
Throws:
See Also:
/** * Set the personal name. If the name contains non US-ASCII * characters, then the name will be encoded using the specified * charset as per RFC 2047. If the name contains only US-ASCII * characters, no encoding is done and the name is used as is. <p> * * @param name personal name * @param charset MIME charset to be used to encode the name as * per RFC 2047 * @see #setPersonal(String) * @exception UnsupportedEncodingException if the charset encoding * fails. */
public void setPersonal(String name, String charset) throws UnsupportedEncodingException { personal = name; if (name != null) encodedPersonal = MimeUtility.encodeWord(name, charset, null); else encodedPersonal = null; }
Set the personal name. If the name contains non US-ASCII characters, then the name will be encoded using the platform's default charset. If the name contains only US-ASCII characters, no encoding is done and the name is used as is.

Params:
  • name – personal name
Throws:
See Also:
/** * Set the personal name. If the name contains non US-ASCII * characters, then the name will be encoded using the platform's * default charset. If the name contains only US-ASCII characters, * no encoding is done and the name is used as is. <p> * * @param name personal name * @see #setPersonal(String name, String charset) * @exception UnsupportedEncodingException if the charset encoding * fails. */
public void setPersonal(String name) throws UnsupportedEncodingException { personal = name; if (name != null) encodedPersonal = MimeUtility.encodeWord(name); else encodedPersonal = null; }
Get the email address.
Returns: email address
/** * Get the email address. * @return email address */
public String getAddress() { return address; }
Get the personal name. If the name is encoded as per RFC 2047, it is decoded and converted into Unicode. If the decoding or conversion fails, the raw data is returned as is.
Returns: personal name
/** * Get the personal name. If the name is encoded as per RFC 2047, * it is decoded and converted into Unicode. If the decoding or * conversion fails, the raw data is returned as is. * * @return personal name */
public String getPersonal() { if (personal != null) return personal; if (encodedPersonal != null) { try { personal = MimeUtility.decodeText(encodedPersonal); return personal; } catch (Exception ex) { // 1. ParseException: either its an unencoded string or // it can't be parsed // 2. UnsupportedEncodingException: can't decode it. return encodedPersonal; } } // No personal or encodedPersonal, return null return null; }
Convert this address into a RFC 822 / RFC 2047 encoded address. The resulting string contains only US-ASCII characters, and hence is mail-safe.
Returns: possibly encoded address string
/** * Convert this address into a RFC 822 / RFC 2047 encoded address. * The resulting string contains only US-ASCII characters, and * hence is mail-safe. * * @return possibly encoded address string */
@Override public String toString() { String a = address == null ? "" : address; if (encodedPersonal == null && personal != null) try { encodedPersonal = MimeUtility.encodeWord(personal); } catch (UnsupportedEncodingException ex) { } if (encodedPersonal != null) return quotePhrase(encodedPersonal) + " <" + a + ">"; else if (isGroup() || isSimple()) return a; else return "<" + a + ">"; }
Returns a properly formatted address (RFC 822 syntax) of Unicode characters.
Returns: Unicode address string
Since: JavaMail 1.2
/** * Returns a properly formatted address (RFC 822 syntax) of * Unicode characters. * * @return Unicode address string * @since JavaMail 1.2 */
public String toUnicodeString() { String p = getPersonal(); if (p != null) return quotePhrase(p) + " <" + address + ">"; else if (isGroup() || isSimple()) return address; else return "<" + address + ">"; } /* * quotePhrase() quotes the words within a RFC822 phrase. * * This is tricky, since a phrase is defined as 1 or more * RFC822 words, separated by LWSP. Now, a word that contains * LWSP is supposed to be quoted, and this is exactly what the * MimeUtility.quote() method does. However, when dealing with * a phrase, any LWSP encountered can be construed to be the * separator between words, and not part of the words themselves. * To deal with this funkiness, we have the below variant of * MimeUtility.quote(), which essentially ignores LWSP when * deciding whether to quote a word. * * It aint pretty, but it gets the job done :) */ private static final String rfc822phrase = HeaderTokenizer.RFC822.replace(' ', '\0').replace('\t', '\0'); private static String quotePhrase(String phrase) { int len = phrase.length(); boolean needQuoting = false; for (int i = 0; i < len; i++) { char c = phrase.charAt(i); if (c == '"' || c == '\\') { // need to escape them and then quote the whole string StringBuilder sb = new StringBuilder(len + 3); sb.append('"'); for (int j = 0; j < len; j++) { char cc = phrase.charAt(j); if (cc == '"' || cc == '\\') // Escape the character sb.append('\\'); sb.append(cc); } sb.append('"'); return sb.toString(); } else if ((c < 040 && c != '\r' && c != '\n' && c != '\t') || (c >= 0177 && !allowUtf8) || rfc822phrase.indexOf(c) >= 0) // These characters cause the string to be quoted needQuoting = true; } if (needQuoting) { StringBuilder sb = new StringBuilder(len + 2); sb.append('"').append(phrase).append('"'); return sb.toString(); } else return phrase; } private static String unquote(String s) { if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) { s = s.substring(1, s.length() - 1); // check for any escaped characters if (s.indexOf('\\') >= 0) { StringBuilder sb = new StringBuilder(s.length()); // approx for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '\\' && i < s.length() - 1) c = s.charAt(++i); sb.append(c); } s = sb.toString(); } } return s; }
The equality operator.
/** * The equality operator. */
@Override public boolean equals(Object a) { if (!(a instanceof InternetAddress)) return false; String s = ((InternetAddress)a).getAddress(); if (s == address) return true; if (address != null && address.equalsIgnoreCase(s)) return true; return false; }
Compute a hash code for the address.
/** * Compute a hash code for the address. */
@Override public int hashCode() { if (address == null) return 0; else return address.toLowerCase(Locale.ENGLISH).hashCode(); }
Convert the given array of InternetAddress objects into a comma separated sequence of address strings. The resulting string contains only US-ASCII characters, and hence is mail-safe.

Params:
  • addresses – array of InternetAddress objects
Throws:
  • ClassCastException – if any address object in the given array is not an InternetAddress object. Note that this is a RuntimeException.
Returns: comma separated string of addresses
/** * Convert the given array of InternetAddress objects into * a comma separated sequence of address strings. The * resulting string contains only US-ASCII characters, and * hence is mail-safe. <p> * * @param addresses array of InternetAddress objects * @exception ClassCastException if any address object in the * given array is not an InternetAddress object. Note * that this is a RuntimeException. * @return comma separated string of addresses */
public static String toString(Address[] addresses) { return toString(addresses, 0); }
Convert the given array of InternetAddress objects into a comma separated sequence of address strings. The resulting string contains Unicode characters.

Params:
  • addresses – array of InternetAddress objects
Throws:
  • ClassCastException – if any address object in the given array is not an InternetAddress object. Note that this is a RuntimeException.
Returns: comma separated string of addresses
Since: JavaMail 1.6
/** * Convert the given array of InternetAddress objects into * a comma separated sequence of address strings. The * resulting string contains Unicode characters. <p> * * @param addresses array of InternetAddress objects * @exception ClassCastException if any address object in the * given array is not an InternetAddress object. Note * that this is a RuntimeException. * @return comma separated string of addresses * @since JavaMail 1.6 */
public static String toUnicodeString(Address[] addresses) { return toUnicodeString(addresses, 0); }
Convert the given array of InternetAddress objects into a comma separated sequence of address strings. The resulting string contains only US-ASCII characters, and hence is mail-safe.

The 'used' parameter specifies the number of character positions already taken up in the field into which the resulting address sequence string is to be inserted. It is used to determine the line-break positions in the resulting address sequence string.

Params:
  • addresses – array of InternetAddress objects
  • used – number of character positions already used, in the field into which the address string is to be inserted.
Throws:
  • ClassCastException – if any address object in the given array is not an InternetAddress object. Note that this is a RuntimeException.
Returns: comma separated string of addresses
/** * Convert the given array of InternetAddress objects into * a comma separated sequence of address strings. The * resulting string contains only US-ASCII characters, and * hence is mail-safe. <p> * * The 'used' parameter specifies the number of character positions * already taken up in the field into which the resulting address * sequence string is to be inserted. It is used to determine the * line-break positions in the resulting address sequence string. * * @param addresses array of InternetAddress objects * @param used number of character positions already used, in * the field into which the address string is to * be inserted. * @exception ClassCastException if any address object in the * given array is not an InternetAddress object. Note * that this is a RuntimeException. * @return comma separated string of addresses */
public static String toString(Address[] addresses, int used) { if (addresses == null || addresses.length == 0) return null; StringBuilder sb = new StringBuilder(); for (int i = 0; i < addresses.length; i++) { if (i != 0) { // need to append comma sb.append(", "); used += 2; } // prefer not to split a single address across lines so used=0 below String s = MimeUtility.fold(0, addresses[i].toString()); int len = lengthOfFirstSegment(s); // length till CRLF if (used + len > 76) { // overflows ... // smash trailing space from ", " above int curlen = sb.length(); if (curlen > 0 && sb.charAt(curlen - 1) == ' ') sb.setLength(curlen - 1); sb.append("\r\n\t"); // .. start new continuation line used = 8; // account for the starting <tab> char } sb.append(s); used = lengthOfLastSegment(s, used); } return sb.toString(); }
Convert the given array of InternetAddress objects into a comma separated sequence of address strings. The resulting string contains Unicode characters.

The 'used' parameter specifies the number of character positions already taken up in the field into which the resulting address sequence string is to be inserted. It is used to determine the line-break positions in the resulting address sequence string.

Params:
  • addresses – array of InternetAddress objects
  • used – number of character positions already used, in the field into which the address string is to be inserted.
Throws:
  • ClassCastException – if any address object in the given array is not an InternetAddress object. Note that this is a RuntimeException.
Returns: comma separated string of addresses
Since: JavaMail 1.6
/** * Convert the given array of InternetAddress objects into * a comma separated sequence of address strings. The * resulting string contains Unicode characters. <p> * * The 'used' parameter specifies the number of character positions * already taken up in the field into which the resulting address * sequence string is to be inserted. It is used to determine the * line-break positions in the resulting address sequence string. * * @param addresses array of InternetAddress objects * @param used number of character positions already used, in * the field into which the address string is to * be inserted. * @exception ClassCastException if any address object in the * given array is not an InternetAddress object. Note * that this is a RuntimeException. * @return comma separated string of addresses * @since JavaMail 1.6 */
/* * XXX - This is exactly the same as the above, except it uses * toUnicodeString instead of toString. * XXX - Since the line length restrictions are in bytes, not characters, * we convert all non-ASCII addresses to UTF-8 byte strings, * which we then convert to ISO-8859-1 Strings where every * character respresents one UTF-8 byte. At the end we reverse * the conversion to get back to a correct Unicode string. * This is a hack to allow all the other character-based methods * to work properly with UTF-8 bytes. */ public static String toUnicodeString(Address[] addresses, int used) { if (addresses == null || addresses.length == 0) return null; StringBuilder sb = new StringBuilder(); boolean sawNonAscii = false; for (int i = 0; i < addresses.length; i++) { if (i != 0) { // need to append comma sb.append(", "); used += 2; } // prefer not to split a single address across lines so used=0 below String as = ((InternetAddress)addresses[i]).toUnicodeString(); if (MimeUtility.checkAscii(as) != MimeUtility.ALL_ASCII) { sawNonAscii = true; as = new String(as.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); } String s = MimeUtility.fold(0, as); int len = lengthOfFirstSegment(s); // length till CRLF if (used + len > 76) { // overflows ... // smash trailing space from ", " above int curlen = sb.length(); if (curlen > 0 && sb.charAt(curlen - 1) == ' ') sb.setLength(curlen - 1); sb.append("\r\n\t"); // .. start new continuation line used = 8; // account for the starting <tab> char } sb.append(s); used = lengthOfLastSegment(s, used); } String ret = sb.toString(); if (sawNonAscii) ret = new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); return ret; } /* * Return the length of the first segment within this string. * If no segments exist, the length of the whole line is returned. */ private static int lengthOfFirstSegment(String s) { int pos; if ((pos = s.indexOf("\r\n")) != -1) return pos; else return s.length(); } /* * Return the length of the last segment within this string. * If no segments exist, the length of the whole line plus * <code>used</code> is returned. */ private static int lengthOfLastSegment(String s, int used) { int pos; if ((pos = s.lastIndexOf("\r\n")) != -1) return s.length() - pos - 2; else return s.length() + used; }
Return an InternetAddress object representing the current user. The entire email address may be specified in the "mail.from" property. If not set, the "mail.user" and "mail.host" properties are tried. If those are not set, the "user.name" property and InetAddress.getLocalHost method are tried. Security exceptions that may occur while accessing this information are ignored. If it is not possible to determine an email address, null is returned.
Params:
  • session – Session object used for property lookup
Returns: current user's email address
/** * Return an InternetAddress object representing the current user. * The entire email address may be specified in the "mail.from" * property. If not set, the "mail.user" and "mail.host" properties * are tried. If those are not set, the "user.name" property and * <code>InetAddress.getLocalHost</code> method are tried. * Security exceptions that may occur while accessing this information * are ignored. If it is not possible to determine an email address, * null is returned. * * @param session Session object used for property lookup * @return current user's email address */
public static InternetAddress getLocalAddress(Session session) { try { return _getLocalAddress(session); } catch (SecurityException sex) { // ignore it } catch (AddressException ex) { // ignore it } catch (UnknownHostException ex) { } // ignore it return null; }
A package-private version of getLocalAddress that doesn't swallow the exception. Used by MimeMessage.setFrom() to report the reason for the failure.
/** * A package-private version of getLocalAddress that doesn't swallow * the exception. Used by MimeMessage.setFrom() to report the reason * for the failure. */
// package-private static InternetAddress _getLocalAddress(Session session) throws SecurityException, AddressException, UnknownHostException { String user = null, host = null, address = null; if (session == null) { user = System.getProperty("user.name"); host = getLocalHostName(); } else { address = session.getProperty("mail.from"); if (address == null) { user = session.getProperty("mail.user"); if (user == null || user.length() == 0) user = session.getProperty("user.name"); if (user == null || user.length() == 0) user = System.getProperty("user.name"); host = session.getProperty("mail.host"); if (host == null || host.length() == 0) host = getLocalHostName(); } } if (address == null && user != null && user.length() != 0 && host != null && host.length() != 0) address = MimeUtility.quote(user.trim(), specialsNoDot + "\t ") + "@" + host; if (address == null) return null; return new InternetAddress(address); }
Get the local host name from InetAddress and return it in a form suitable for use in an email address.
/** * Get the local host name from InetAddress and return it in a form * suitable for use in an email address. */
private static String getLocalHostName() throws UnknownHostException { String host = null; InetAddress me = InetAddress.getLocalHost(); if (me != null) { // try canonical host name first if (useCanonicalHostName) host = me.getCanonicalHostName(); if (host == null) host = me.getHostName(); // if we can't get our name, use local address literal if (host == null) host = me.getHostAddress(); if (host != null && host.length() > 0 && isInetAddressLiteral(host)) host = '[' + host + ']'; } return host; }
Is the address an IPv4 or IPv6 address literal, which needs to be enclosed in "[]" in an email address? IPv4 literals contain decimal digits and dots, IPv6 literals contain hex digits, dots, and colons. We're lazy and don't check the exact syntax, just the allowed characters; strings that have only the allowed characters in a literal but don't meet the syntax requirements for a literal definitely can't be a host name and thus will fail later when used as an address literal.
/** * Is the address an IPv4 or IPv6 address literal, which needs to * be enclosed in "[]" in an email address? IPv4 literals contain * decimal digits and dots, IPv6 literals contain hex digits, dots, * and colons. We're lazy and don't check the exact syntax, just * the allowed characters; strings that have only the allowed * characters in a literal but don't meet the syntax requirements * for a literal definitely can't be a host name and thus will fail * later when used as an address literal. */
private static boolean isInetAddressLiteral(String addr) { boolean sawHex = false, sawColon = false; for (int i = 0; i < addr.length(); i++) { char c = addr.charAt(i); if (c >= '0' && c <= '9') ; // digits always ok else if (c == '.') ; // dot always ok else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) sawHex = true; // need to see a colon too else if (c == ':') sawColon = true; else return false; // anything else, definitely not a literal } return !sawHex || sawColon; }
Parse the given comma separated sequence of addresses into InternetAddress objects. Addresses must follow RFC822 syntax.
Params:
  • addresslist – comma separated address strings
Throws:
Returns: array of InternetAddress objects
/** * Parse the given comma separated sequence of addresses into * InternetAddress objects. Addresses must follow RFC822 syntax. * * @param addresslist comma separated address strings * @return array of InternetAddress objects * @exception AddressException if the parse failed */
public static InternetAddress[] parse(String addresslist) throws AddressException { return parse(addresslist, true); }
Parse the given sequence of addresses into InternetAddress objects. If strict is false, simple email addresses separated by spaces are also allowed. If strict is true, many (but not all) of the RFC822 syntax rules are enforced. In particular, even if strict is true, addresses composed of simple names (with no "@domain" part) are allowed. Such "illegal" addresses are not uncommon in real messages.

Non-strict parsing is typically used when parsing a list of mail addresses entered by a human. Strict parsing is typically used when parsing address headers in mail messages.

Params:
  • addresslist – comma separated address strings
  • strict – enforce RFC822 syntax
Throws:
Returns: array of InternetAddress objects
/** * Parse the given sequence of addresses into InternetAddress * objects. If <code>strict</code> is false, simple email addresses * separated by spaces are also allowed. If <code>strict</code> is * true, many (but not all) of the RFC822 syntax rules are enforced. * In particular, even if <code>strict</code> is true, addresses * composed of simple names (with no "@domain" part) are allowed. * Such "illegal" addresses are not uncommon in real messages. <p> * * Non-strict parsing is typically used when parsing a list of * mail addresses entered by a human. Strict parsing is typically * used when parsing address headers in mail messages. * * @param addresslist comma separated address strings * @param strict enforce RFC822 syntax * @return array of InternetAddress objects * @exception AddressException if the parse failed */
public static InternetAddress[] parse(String addresslist, boolean strict) throws AddressException { return parse(addresslist, strict, false); }
Parse the given sequence of addresses into InternetAddress objects. If strict is false, the full syntax rules for individual addresses are not enforced. If strict is true, many (but not all) of the RFC822 syntax rules are enforced.

To better support the range of "invalid" addresses seen in real messages, this method enforces fewer syntax rules than the parse method when the strict flag is false and enforces more rules when the strict flag is true. If the strict flag is false and the parse is successful in separating out an email address or addresses, the syntax of the addresses themselves is not checked.

Params:
  • addresslist – comma separated address strings
  • strict – enforce RFC822 syntax
Throws:
Returns: array of InternetAddress objects
Since: JavaMail 1.3
/** * Parse the given sequence of addresses into InternetAddress * objects. If <code>strict</code> is false, the full syntax rules for * individual addresses are not enforced. If <code>strict</code> is * true, many (but not all) of the RFC822 syntax rules are enforced. <p> * * To better support the range of "invalid" addresses seen in real * messages, this method enforces fewer syntax rules than the * <code>parse</code> method when the strict flag is false * and enforces more rules when the strict flag is true. If the * strict flag is false and the parse is successful in separating out an * email address or addresses, the syntax of the addresses themselves * is not checked. * * @param addresslist comma separated address strings * @param strict enforce RFC822 syntax * @return array of InternetAddress objects * @exception AddressException if the parse failed * @since JavaMail 1.3 */
public static InternetAddress[] parseHeader(String addresslist, boolean strict) throws AddressException { return parse(MimeUtility.unfold(addresslist), strict, true); } /* * RFC822 Address parser. * * XXX - This is complex enough that it ought to be a real parser, * not this ad-hoc mess, and because of that, this is not perfect. * * XXX - Deal with encoded Headers too. */ @SuppressWarnings("fallthrough") private static InternetAddress[] parse(String s, boolean strict, boolean parseHdr) throws AddressException { int start, end, index, nesting; int start_personal = -1, end_personal = -1; int length = s.length(); boolean ignoreErrors = parseHdr && !strict; boolean in_group = false; // we're processing a group term boolean route_addr = false; // address came from route-addr term boolean rfc822 = false; // looks like an RFC822 address char c; List<InternetAddress> v = new ArrayList<>(); InternetAddress ma; for (start = end = -1, index = 0; index < length; index++) { c = s.charAt(index); switch (c) { case '(': // We are parsing a Comment. Ignore everything inside. // XXX - comment fields should be parsed as whitespace, // more than one allowed per address rfc822 = true; if (start >= 0 && end == -1) end = index; int pindex = index; for (index++, nesting = 1; index < length && nesting > 0; index++) { c = s.charAt(index); switch (c) { case '\\': index++; // skip both '\' and the escaped char break; case '(': nesting++; break; case ')': nesting--; break; default: break; } } if (nesting > 0) { if (!ignoreErrors) throw new AddressException("Missing ')'", s, index); // pretend the first paren was a regular character and // continue parsing after it index = pindex + 1; break; } index--; // point to closing paren if (start_personal == -1) start_personal = pindex + 1; if (end_personal == -1) end_personal = index; break; case ')': if (!ignoreErrors) throw new AddressException("Missing '('", s, index); // pretend the left paren was a regular character and // continue parsing if (start == -1) start = index; break; case '<': rfc822 = true; if (route_addr) { if (!ignoreErrors) throw new AddressException( "Extra route-addr", s, index); // assume missing comma between addresses if (start == -1) { route_addr = false; rfc822 = false; start = end = -1; break; // nope, nothing there } if (!in_group) { // got a token, add this to our InternetAddress list if (end == -1) // should never happen end = index; String addr = s.substring(start, end).trim(); ma = new InternetAddress(); ma.setAddress(addr); if (start_personal >= 0) { ma.encodedPersonal = unquote( s.substring(start_personal, end_personal). trim()); } v.add(ma); route_addr = false; rfc822 = false; start = end = -1; start_personal = end_personal = -1; // continue processing this new address... } } int rindex = index; boolean inquote = false; outf: for (index++; index < length; index++) { c = s.charAt(index); switch (c) { case '\\': // XXX - is this needed? index++; // skip both '\' and the escaped char break; case '"': inquote = !inquote; break; case '>': if (inquote) continue; break outf; // out of for loop default: break; } } // did we find a matching quote? if (inquote) { if (!ignoreErrors) throw new AddressException("Missing '\"'", s, index); // didn't find matching quote, try again ignoring quotes // (e.g., ``<"@foo.com>'') outq: for (index = rindex + 1; index < length; index++) { c = s.charAt(index); if (c == '\\') // XXX - is this needed? index++; // skip both '\' and the escaped char else if (c == '>') break; } } // did we find a terminating '>'? if (index >= length) { if (!ignoreErrors) throw new AddressException("Missing '>'", s, index); // pretend the "<" was a regular character and // continue parsing after it (e.g., ``<@foo.com'') index = rindex + 1; if (start == -1) start = rindex; // back up to include "<" break; } if (!in_group) { if (start >= 0) { // seen some characters? use them as the personal name start_personal = start; end_personal = rindex; } start = rindex + 1; } route_addr = true; end = index; break; case '>': if (!ignoreErrors) throw new AddressException("Missing '<'", s, index); // pretend the ">" was a regular character and // continue parsing (e.g., ``>@foo.com'') if (start == -1) start = index; break; case '"': // parse quoted string int qindex = index; rfc822 = true; if (start == -1) start = index; outq: for (index++; index < length; index++) { c = s.charAt(index); switch (c) { case '\\': index++; // skip both '\' and the escaped char break; case '"': break outq; // out of for loop default: break; } } if (index >= length) { if (!ignoreErrors) throw new AddressException("Missing '\"'", s, index); // pretend the quote was a regular character and // continue parsing after it (e.g., ``"@foo.com'') index = qindex + 1; } break; case '[': // a domain-literal, probably int lindex = index; rfc822 = true; if (start == -1) start = index; outb: for (index++; index < length; index++) { c = s.charAt(index); switch (c) { case '\\': index++; // skip both '\' and the escaped char break; case ']': break outb; // out of for loop default: break; } } if (index >= length) { if (!ignoreErrors) throw new AddressException("Missing ']'", s, index); // pretend the "[" was a regular character and // continue parsing after it (e.g., ``[@foo.com'') index = lindex + 1; } break; case ';': if (start == -1) { route_addr = false; rfc822 = false; start = end = -1; break; // nope, nothing there } if (in_group) { in_group = false; /* * If parsing headers, but not strictly, peek ahead. * If next char is "@", treat the group name * like the local part of the address, e.g., * "Undisclosed-Recipient:;@java.sun.com". */ if (parseHdr && !strict && index + 1 < length && s.charAt(index + 1) == '@') break; ma = new InternetAddress(); end = index + 1; ma.setAddress(s.substring(start, end).trim()); v.add(ma); route_addr = false; rfc822 = false; start = end = -1; start_personal = end_personal = -1; break; } if (!ignoreErrors) throw new AddressException( "Illegal semicolon, not in group", s, index); // otherwise, parsing a header; treat semicolon like comma // fall through to comma case... case ',': // end of an address, probably if (start == -1) { route_addr = false; rfc822 = false; start = end = -1; break; // nope, nothing there } if (in_group) { route_addr = false; break; } // got a token, add this to our InternetAddress list if (end == -1) end = index; String addr = s.substring(start, end).trim(); String pers = null; if (rfc822 && start_personal >= 0) { pers = unquote( s.substring(start_personal, end_personal).trim()); if (pers.trim().length() == 0) pers = null; } /* * If the personal name field has an "@" and the address * field does not, assume they were reversed, e.g., * ``"joe doe" (john.doe@example.com)''. */ if (parseHdr && !strict && pers != null && pers.indexOf('@') >= 0 && addr.indexOf('@') < 0 && addr.indexOf('!') < 0) { String tmp = addr; addr = pers; pers = tmp; } if (rfc822 || strict || parseHdr) { if (!ignoreErrors) checkAddress(addr, route_addr, false); ma = new InternetAddress(); ma.setAddress(addr); if (pers != null) ma.encodedPersonal = pers; v.add(ma); } else { // maybe we passed over more than one space-separated addr StringTokenizer st = new StringTokenizer(addr); while (st.hasMoreTokens()) { String a = st.nextToken(); checkAddress(a, false, false); ma = new InternetAddress(); ma.setAddress(a); v.add(ma); } } route_addr = false; rfc822 = false; start = end = -1; start_personal = end_personal = -1; break; case ':': rfc822 = true; if (in_group) if (!ignoreErrors) throw new AddressException("Nested group", s, index); if (start == -1) start = index; if (parseHdr && !strict) { /* * If next char is a special character that can't occur at * the start of a valid address, treat the group name * as the entire address, e.g., "Date:, Tue", "Re:@foo". */ if (index + 1 < length) { String addressSpecials = ")>[]:@\\,."; char nc = s.charAt(index + 1); if (addressSpecials.indexOf(nc) >= 0) { if (nc != '@') break; // don't change in_group /* * Handle a common error: * ``Undisclosed-Recipient:@example.com;'' * * Scan ahead. If we find a semicolon before * one of these other special characters, * consider it to be a group after all. */ for (int i = index + 2; i < length; i++) { nc = s.charAt(i); if (nc == ';') break; if (addressSpecials.indexOf(nc) >= 0) break; } if (nc == ';') break; // don't change in_group } } // ignore bogus "mailto:" prefix in front of an address, // or bogus mail header name included in the address field String gname = s.substring(start, index); if (ignoreBogusGroupName && (gname.equalsIgnoreCase("mailto") || gname.equalsIgnoreCase("From") || gname.equalsIgnoreCase("To") || gname.equalsIgnoreCase("Cc") || gname.equalsIgnoreCase("Subject") || gname.equalsIgnoreCase("Re"))) start = -1; // we're not really in a group else in_group = true; } else in_group = true; break; // Ignore whitespace case ' ': case '\t': case '\r': case '\n': break; default: if (start == -1) start = index; break; } } if (start >= 0) { /* * The last token, add this to our InternetAddress list. * Note that this block of code should be identical to the * block above for "case ','". */ if (end == -1) end = length; String addr = s.substring(start, end).trim(); String pers = null; if (rfc822 && start_personal >= 0) { pers = unquote( s.substring(start_personal, end_personal).trim()); if (pers.trim().length() == 0) pers = null; } /* * If the personal name field has an "@" and the address * field does not, assume they were reversed, e.g., * ``"joe doe" (john.doe@example.com)''. */ if (parseHdr && !strict && pers != null && pers.indexOf('@') >= 0 && addr.indexOf('@') < 0 && addr.indexOf('!') < 0) { String tmp = addr; addr = pers; pers = tmp; } if (rfc822 || strict || parseHdr) { if (!ignoreErrors) checkAddress(addr, route_addr, false); ma = new InternetAddress(); ma.setAddress(addr); if (pers != null) ma.encodedPersonal = pers; v.add(ma); } else { // maybe we passed over more than one space-separated addr StringTokenizer st = new StringTokenizer(addr); while (st.hasMoreTokens()) { String a = st.nextToken(); checkAddress(a, false, false); ma = new InternetAddress(); ma.setAddress(a); v.add(ma); } } } InternetAddress[] a = new InternetAddress[v.size()]; v.toArray(a); return a; }
Validate that this address conforms to the syntax rules of RFC 822. The current implementation checks many, but not all, syntax rules. Note that even though the syntax of the address may be correct, there's no guarantee that a mailbox of that name exists.
Throws:
  • AddressException – if the address isn't valid.
Since: JavaMail 1.3
/** * Validate that this address conforms to the syntax rules of * RFC 822. The current implementation checks many, but not * all, syntax rules. Note that even though the syntax of * the address may be correct, there's no guarantee that a * mailbox of that name exists. * * @exception AddressException if the address isn't valid. * @since JavaMail 1.3 */
public void validate() throws AddressException { if (isGroup()) getGroup(true); // throw away the result else checkAddress(getAddress(), true, true); } private static final String specialsNoDotNoAt = "()<>,;:\\\"[]"; private static final String specialsNoDot = specialsNoDotNoAt + "@";
Check that the address is a valid "mailbox" per RFC822. (We also allow simple names.) XXX - much more to check XXX - doesn't handle domain-literals properly (but no one uses them)
/** * Check that the address is a valid "mailbox" per RFC822. * (We also allow simple names.) * * XXX - much more to check * XXX - doesn't handle domain-literals properly (but no one uses them) */
private static void checkAddress(String addr, boolean routeAddr, boolean validate) throws AddressException { int i, start = 0; if (addr == null) throw new AddressException("Address is null"); int len = addr.length(); if (len == 0) throw new AddressException("Empty address", addr); /* * routeAddr indicates that the address is allowed * to have an RFC 822 "route". */ if (routeAddr && addr.charAt(0) == '@') { /* * Check for a legal "route-addr": * [@domain[,@domain ...]:]local@domain */ for (start = 0; (i = indexOfAny(addr, ",:", start)) >= 0; start = i+1) { if (addr.charAt(start) != '@') throw new AddressException("Illegal route-addr", addr); if (addr.charAt(i) == ':') { // end of route-addr start = i + 1; break; } } } /* * The rest should be "local@domain", but we allow simply "local" * unless called from validate. * * local-part must follow RFC 822 - no specials except '.' * unless quoted. */ char c = (char)-1; char lastc = (char)-1; boolean inquote = false; for (i = start; i < len; i++) { lastc = c; c = addr.charAt(i); // a quoted-pair is only supposed to occur inside a quoted string, // but some people use it outside so we're more lenient if (c == '\\' || lastc == '\\') continue; if (c == '"') { if (inquote) { // peek ahead, next char must be "@" if (validate && i + 1 < len && addr.charAt(i + 1) != '@') throw new AddressException( "Quote not at end of local address", addr); inquote = false; } else { if (validate && i != 0) throw new AddressException( "Quote not at start of local address", addr); inquote = true; } continue; } else if (c == '\r') { // peek ahead, next char must be LF if (i + 1 < len && addr.charAt(i + 1) != '\n') throw new AddressException( "Quoted local address contains CR without LF", addr); } else if (c == '\n') { /* * CRLF followed by whitespace is allowed in a quoted string. * We allowed naked LF, but ensure LF is always followed by * whitespace to prevent spoofing the end of the header. */ if (i + 1 < len && addr.charAt(i + 1) != ' ' && addr.charAt(i + 1) != '\t') throw new AddressException( "Quoted local address contains newline without whitespace", addr); } else if (c == '.') { if (i == start) throw new AddressException( "Local address starts with dot", addr); if (lastc == '.') throw new AddressException( "Local address contains dot-dot", addr); } if (inquote) continue; if (c == '@') { if (i == 0) throw new AddressException("Missing local name", addr); if (lastc == '.') throw new AddressException( "Local address ends with dot", addr); break; // done with local part } if (c <= 040 || c == 0177) throw new AddressException( "Local address contains control or whitespace", addr); if (specialsNoDot.indexOf(c) >= 0) throw new AddressException( "Local address contains illegal character", addr); } if (inquote) throw new AddressException("Unterminated quote", addr); /* * Done with local part, now check domain. * * Note that the MimeMessage class doesn't remember addresses * as separate objects; it writes them out as headers and then * parses the headers when the addresses are requested. * In order to support the case where a "simple" address is used, * but the address also has a personal name and thus looks like * it should be a valid RFC822 address when parsed, we only check * this if we're explicitly called from the validate method. */ if (c != '@') { if (validate) throw new AddressException("Missing final '@domain'", addr); return; } // check for illegal chars in the domain, but ignore domain literals start = i + 1; if (start >= len) throw new AddressException("Missing domain", addr); if (addr.charAt(start) == '.') throw new AddressException("Domain starts with dot", addr); boolean inliteral = false; for (i = start; i < len; i++) { c = addr.charAt(i); if (c == '[') { if (i != start) throw new AddressException( "Domain literal not at start of domain", addr); inliteral = true; // domain literal, don't validate } else if (c == ']') { if (i != len - 1) throw new AddressException( "Domain literal end not at end of domain", addr); inliteral = false; } else if (c <= 040 || c == 0177) { throw new AddressException( "Domain contains control or whitespace", addr); } else { // RFC 2822 rule //if (specialsNoDot.indexOf(c) >= 0) /* * RFC 1034 rule is more strict * the full rule is: * * <domain> ::= <subdomain> | " " * <subdomain> ::= <label> | <subdomain> "." <label> * <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ] * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> * <let-dig-hyp> ::= <let-dig> | "-" * <let-dig> ::= <letter> | <digit> */ if (!inliteral) { if (!(Character.isLetterOrDigit(c) || c == '-' || c == '.')) throw new AddressException( "Domain contains illegal character", addr); if (c == '.' && lastc == '.') throw new AddressException( "Domain contains dot-dot", addr); } } lastc = c; } if (lastc == '.') throw new AddressException("Domain ends with dot", addr); }
Is this a "simple" address? Simple addresses don't contain quotes or any RFC822 special characters other than '@' and '.'.
/** * Is this a "simple" address? Simple addresses don't contain quotes * or any RFC822 special characters other than '@' and '.'. */
private boolean isSimple() { return address == null || indexOfAny(address, specialsNoDotNoAt) < 0; }
Indicates whether this address is an RFC 822 group address. Note that a group address is different than the mailing list addresses supported by most mail servers. Group addresses are rarely used; see RFC 822 for details.
Returns: true if this address represents a group
Since: JavaMail 1.3
/** * Indicates whether this address is an RFC 822 group address. * Note that a group address is different than the mailing * list addresses supported by most mail servers. Group addresses * are rarely used; see RFC 822 for details. * * @return true if this address represents a group * @since JavaMail 1.3 */
public boolean isGroup() { // quick and dirty check return address != null && address.endsWith(";") && address.indexOf(':') > 0; }
Return the members of a group address. A group may have zero, one, or more members. If this address is not a group, null is returned. The strict parameter controls whether the group list is parsed using strict RFC 822 rules or not. The parsing is done using the parseHeader method.
Params:
  • strict – use strict RFC 822 rules?
Throws:
Returns: array of InternetAddress objects, or null
Since: JavaMail 1.3
/** * Return the members of a group address. A group may have zero, * one, or more members. If this address is not a group, null * is returned. The <code>strict</code> parameter controls whether * the group list is parsed using strict RFC 822 rules or not. * The parsing is done using the <code>parseHeader</code> method. * * @param strict use strict RFC 822 rules? * @return array of InternetAddress objects, or null * @exception AddressException if the group list can't be parsed * @since JavaMail 1.3 */
public InternetAddress[] getGroup(boolean strict) throws AddressException { String addr = getAddress(); if (addr == null) return null; // groups are of the form "name:addr,addr,...;" if (!addr.endsWith(";")) return null; int ix = addr.indexOf(':'); if (ix < 0) return null; // extract the list String list = addr.substring(ix + 1, addr.length() - 1); // parse it and return the individual addresses return InternetAddress.parseHeader(list, strict); }
Return the first index of any of the characters in "any" in "s", or -1 if none are found. This should be a method on String.
/** * Return the first index of any of the characters in "any" in "s", * or -1 if none are found. * * This should be a method on String. */
private static int indexOfAny(String s, String any) { return indexOfAny(s, any, 0); } private static int indexOfAny(String s, String any, int start) { try { int len = s.length(); for (int i = start; i < len; i++) { if (any.indexOf(s.charAt(i)) >= 0) return i; } return -1; } catch (StringIndexOutOfBoundsException e) { return -1; } } /* public static void main(String argv[]) throws Exception { for (int i = 0; i < argv.length; i++) { InternetAddress[] a = InternetAddress.parse(argv[i]); for (int j = 0; j < a.length; j++) { System.out.println("arg " + i + " address " + j + ": " + a[j]); System.out.println("\tAddress: " + a[j].getAddress() + "\tPersonal: " + a[j].getPersonal()); } if (a.length > 1) { System.out.println("address 0 hash code: " + a[0].hashCode()); System.out.println("address 1 hash code: " + a[1].hashCode()); if (a[0].hashCode() == a[1].hashCode()) System.out.println("success, hashcodes equal"); else System.out.println("fail, hashcodes not equal"); if (a[0].equals(a[1])) System.out.println("success, addresses equal"); else System.out.println("fail, addresses not equal"); if (a[1].equals(a[0])) System.out.println("success, addresses equal"); else System.out.println("fail, addresses not equal"); } } } */ }