| /* |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| * |
| * Copyright (c) 1997-2016 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html |
| * or packager/legal/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 packager/legal/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 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 <user@host.domain>". |
| * |
| * @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); |
| |
| /** |
| * 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 |
| */ |
| 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". |
| */ |
| 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 |
| */ |
| 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 |
| StringBuffer sb = new StringBuffer(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 || rfc822phrase.indexOf(c) >= 0) |
| // These characters cause the string to be quoted |
| needQuoting = true; |
| } |
| |
| if (needQuoting) { |
| StringBuffer sb = new StringBuffer(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) { |
| StringBuffer sb = new StringBuffer(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. |
| */ |
| 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. |
| */ |
| 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 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(); |
| } |
| |
| /* |
| * 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>(); |
| 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 |
| rfc822 = true; |
| int lindex = 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); |
| } |
| if (inquote) |
| continue; |
| if (c == '@') { |
| if (i == 0) |
| throw new AddressException("Missing local name", 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"); |
| } |
| } |
| } |
| */ |
| } |