/* * 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 java.nio.charset.StandardCharsets; import javax.mail.*; import com.sun.mail.util.PropUtil; /** * This class represents an Internet email address using the syntax * of RFC822. * Typical address syntax is of the form "user@host.domain" or * "Personal Name <user@host.domain>". * * @author Bill Shannon * @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.
*
* This field and the personal
field track each
* other, so if a subclass sets one of these fields directly, it
* should set the other to null
, so that it is
* suitably recomputed.
*/
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.
*
* Parse the given string and create an InternetAddress.
* See the parse
method for details of the parsing.
* The address is parsed using "strict" parsing.
* This constructor does not perform the additional
* syntax checks that the
* InternetAddress(String address, boolean strict)
* constructor does when strict
is true
.
* This constructor is equivalent to
* InternetAddress(address, false)
.
*
* @param address the address in RFC822 format
* @exception AddressException if the parse failed
*/
public InternetAddress(String address) throws AddressException {
// use our address parsing utility routine to parse the string
InternetAddress a[] = parse(address, true);
// if we got back anything other than a single address, it's an error
if (a.length != 1)
throw new AddressException("Illegal address", address);
/*
* Now copy the contents of the single address we parsed
* into the current object, which will be returned from the
* constructor.
* XXX - this sure is a round-about way of getting this done.
*/
this.address = a[0].address;
this.personal = a[0].personal;
this.encodedPersonal = a[0].encodedPersonal;
}
/**
* Parse the given string and create an InternetAddress.
* If strict
is false, the detailed syntax of the
* address isn't checked.
*
* @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.
* * @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.
* * @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 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 && !allowUtf8) || 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. */ @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.
* * @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.
* * @param addresses array of InternetAddress objects * @exception ClassCastException if any address object in the * given array is not an InternetAddress object. Note * that this is a RuntimeException. * @return comma separated string of addresses * @since JavaMail 1.6 */ public static String toUnicodeString(Address[] addresses) { return toUnicodeString(addresses, 0); } /** * Convert the given array of InternetAddress objects into * a comma separated sequence of address strings. The * resulting string contains only US-ASCII characters, and * hence is mail-safe.
*
* The 'used' parameter specifies the number of character positions
* already taken up in the field into which the resulting address
* sequence string is to be inserted. It is used to determine the
* line-break positions in the resulting address sequence string.
*
* @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
*
* 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
*
* 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
*
* To better support the range of "invalid" addresses seen in real
* messages, this method enforces fewer syntax rules than the
* used
is returned.
*/
private static int lengthOfLastSegment(String s, int used) {
int pos;
if ((pos = s.lastIndexOf("\r\n")) != -1)
return s.length() - pos - 2;
else
return s.length() + used;
}
/**
* Return an InternetAddress object representing the current user.
* The entire email address may be specified in the "mail.from"
* property. If not set, the "mail.user" and "mail.host" properties
* are tried. If those are not set, the "user.name" property and
* InetAddress.getLocalHost
method are tried.
* Security exceptions that may occur while accessing this information
* are ignored. If it is not possible to determine an email address,
* null is returned.
*
* @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 strict
is false, simple email addresses
* separated by spaces are also allowed. If strict
is
* true, many (but not all) of the RFC822 syntax rules are enforced.
* In particular, even if strict
is true, addresses
* composed of simple names (with no "@domain" part) are allowed.
* Such "illegal" addresses are not uncommon in real messages. strict
is false, the full syntax rules for
* individual addresses are not enforced. If strict
is
* true, many (but not all) of the RFC822 syntax rules are enforced. parse
method when the strict flag is false
* and enforces more rules when the strict flag is true. If the
* strict flag is false and the parse is successful in separating out an
* email address or addresses, the syntax of the addresses themselves
* is not checked.
*
* @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