javax.mail.internet.InternetAddress.java Source code

Java tutorial

Introduction

Here is the source code for javax.mail.internet.InternetAddress.java

Source

/*
 * 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 <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.
     */
    protected String personal;

    /**
     * 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.
     */
    public InternetAddress() {
    }

    /**
     * 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 <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.
     *
     * @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.
     *
     * @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
     */
    @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".
     */
    @Override
    public String getType() {
        return "rfc822";
    }

    /**
     * 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. <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. <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.
     * @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.
     *
     * @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.
     *
     * @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.
     *   
     * @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.
     */
    @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.
     */
    @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. <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. <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. <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. <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
     * <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.
     */
    // 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.
     */
    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.
     */
    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.
     *
     * @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 <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 <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.
     *
     * @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)
     */
    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 '.'.
     */
    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.
     *
     * @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 <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.
     */
    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");
       }
    }
    }
    */
}