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