| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package javax.mail.internet; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| |
| import javax.mail.Address; |
| import javax.mail.Session; |
| |
| /** |
| * A representation of an Internet email address as specified by RFC822 in |
| * conjunction with a human-readable personal name that can be encoded as |
| * specified by RFC2047. |
| * A typical address is "user@host.domain" and personal name "Joe User" |
| * |
| * @version $Rev$ $Date$ |
| */ |
| public class InternetAddress extends Address implements Cloneable { |
| |
| private static final long serialVersionUID = -7507595530758302903L; |
| |
| /** |
| * The address in RFC822 format. |
| */ |
| protected String address; |
| |
| /** |
| * The personal name in RFC2047 format. |
| * Subclasses must ensure that this field is updated if the personal field |
| * is updated; alternatively, it can be invalidated by setting to null |
| * which will cause it to be recomputed. |
| */ |
| protected String encodedPersonal; |
| |
| /** |
| * The personal name as a Java String. |
| * Subclasses must ensure that this field is updated if the encodedPersonal field |
| * is updated; alternatively, it can be invalidated by setting to null |
| * which will cause it to be recomputed. |
| */ |
| protected String personal; |
| |
| public InternetAddress() { |
| } |
| |
| public InternetAddress(final String address) throws AddressException { |
| this(address, true); |
| } |
| |
| public InternetAddress(final String address, final boolean strict) throws AddressException { |
| // use the parse method to process the address. This has the wierd side effect of creating a new |
| // InternetAddress instance to create an InternetAddress, but these are lightweight objects and |
| // we need access to multiple pieces of data from the parsing process. |
| final AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT); |
| |
| final InternetAddress parsedAddress = parser.parseAddress(); |
| // copy the important information, which right now is just the address and |
| // personal info. |
| this.address = parsedAddress.address; |
| this.personal = parsedAddress.personal; |
| this.encodedPersonal = parsedAddress.encodedPersonal; |
| } |
| |
| public InternetAddress(final String address, final String personal) throws UnsupportedEncodingException { |
| this(address, personal, null); |
| } |
| |
| public InternetAddress(final String address, final String personal, final String charset) throws UnsupportedEncodingException { |
| this.address = address; |
| setPersonal(personal, charset); |
| } |
| |
| /** |
| * Clone this object. |
| * |
| * @return a copy of this object as created by Object.clone() |
| */ |
| @Override |
| public Object clone() { |
| try { |
| return super.clone(); |
| } catch (final CloneNotSupportedException e) { |
| throw new Error(); |
| } |
| } |
| |
| /** |
| * Return the type of this address. |
| * |
| * @return the type of this address; always "rfc822" |
| */ |
| @Override |
| public String getType() { |
| return "rfc822"; |
| } |
| |
| /** |
| * Set the address. |
| * No validation is performed; validate() can be used to check if it is valid. |
| * |
| * @param address the address to set |
| */ |
| public void setAddress(final String address) { |
| this.address = address; |
| } |
| |
| /** |
| * Set the personal name. |
| * The name is first checked to see if it can be encoded; if this fails then an |
| * UnsupportedEncodingException is thrown and no fields are modified. |
| * |
| * @param name the new personal name |
| * @param charset the charset to use; see {@link MimeUtility#encodeWord(String, String, String) MimeUtilityencodeWord} |
| * @throws UnsupportedEncodingException if the name cannot be encoded |
| */ |
| public void setPersonal(final String name, final String charset) throws UnsupportedEncodingException { |
| personal = name; |
| if (name != null) { |
| encodedPersonal = MimeUtility.encodeWord(name, charset, null); |
| } |
| else { |
| encodedPersonal = null; |
| } |
| } |
| |
| /** |
| * Set the personal name. |
| * The name is first checked to see if it can be encoded using {@link MimeUtility#encodeWord(String)}; if this fails then an |
| * UnsupportedEncodingException is thrown and no fields are modified. |
| * |
| * @param name the new personal name |
| * @throws UnsupportedEncodingException if the name cannot be encoded |
| */ |
| public void setPersonal(final String name) throws UnsupportedEncodingException { |
| personal = name; |
| if (name != null) { |
| encodedPersonal = MimeUtility.encodeWord(name); |
| } |
| else { |
| encodedPersonal = null; |
| } |
| } |
| |
| /** |
| * Return the address. |
| * |
| * @return the address |
| */ |
| public String getAddress() { |
| return address; |
| } |
| |
| /** |
| * Return the personal name. |
| * If the personal field is null, then an attempt is made to decode the encodedPersonal |
| * field using {@link MimeUtility#decodeWord(String)}; if this is sucessful, then |
| * the personal field is updated with that value and returned; if there is a problem |
| * decoding the text then the raw value from encodedPersonal is returned. |
| * |
| * @return the personal name |
| */ |
| public String getPersonal() { |
| if (personal == null && encodedPersonal != null) { |
| try { |
| personal = MimeUtility.decodeWord(encodedPersonal); |
| } catch (final ParseException e) { |
| return encodedPersonal; |
| } catch (final UnsupportedEncodingException e) { |
| return encodedPersonal; |
| } |
| } |
| return personal; |
| } |
| |
| /** |
| * Return the encoded form of the personal name. |
| * If the encodedPersonal field is null, then an attempt is made to encode the |
| * personal field using {@link MimeUtility#encodeWord(String)}; if this is |
| * successful then the encodedPersonal field is updated with that value and returned; |
| * if there is a problem encoding the text then null is returned. |
| * |
| * @return the encoded form of the personal name |
| */ |
| private String getEncodedPersonal() { |
| if (encodedPersonal == null && personal != null) { |
| try { |
| encodedPersonal = MimeUtility.encodeWord(personal); |
| } catch (final UnsupportedEncodingException e) { |
| // as we could not encode this, return null |
| return null; |
| } |
| } |
| return encodedPersonal; |
| } |
| |
| |
| /** |
| * Return a string representation of this address using only US-ASCII characters. |
| * |
| * @return a string representation of this address |
| */ |
| @Override |
| public String toString() { |
| // group addresses are always returned without modification. |
| if (isGroup()) { |
| return address; |
| } |
| |
| // if we have personal information, then we need to return this in the route-addr form: |
| // "personal <address>". If there is no personal information, then we typically return |
| // the address without the angle brackets. However, if the address contains anything other |
| // than atoms, '@', and '.' (e.g., uses domain literals, has specified routes, or uses |
| // quoted strings in the local-part), we bracket the address. |
| final String p = getEncodedPersonal(); |
| if (p == null) { |
| return formatAddress(address); |
| } |
| else { |
| final StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3); |
| buf.append(AddressParser.quoteString(p)); |
| buf.append(" <").append(address).append(">"); |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * Check the form of an address, and enclose it within brackets |
| * if they are required for this address form. |
| * |
| * @param a The source address. |
| * |
| * @return A formatted address, which can be the original address string. |
| */ |
| private String formatAddress(final String a) |
| { |
| // this could be a group address....we don't muck with those. |
| if (address.endsWith(";") && address.indexOf(":") > 0) { |
| return address; |
| } |
| |
| if (AddressParser.containsCharacters(a, "()<>,;:\"[]")) { |
| final StringBuffer buf = new StringBuffer(address.length() + 3); |
| buf.append("<").append(address).append(">"); |
| return buf.toString(); |
| } |
| return address; |
| } |
| |
| /** |
| * Return a string representation of this address using Unicode characters. |
| * |
| * @return a string representation of this address |
| */ |
| public String toUnicodeString() { |
| // group addresses are always returned without modification. |
| if (isGroup()) { |
| return address; |
| } |
| |
| // if we have personal information, then we need to return this in the route-addr form: |
| // "personal <address>". If there is no personal information, then we typically return |
| // the address without the angle brackets. However, if the address contains anything other |
| // than atoms, '@', and '.' (e.g., uses domain literals, has specified routes, or uses |
| // quoted strings in the local-part), we bracket the address. |
| |
| // NB: The difference between toString() and toUnicodeString() is the use of getPersonal() |
| // vs. getEncodedPersonal() for the personal portion. If the personal information contains only |
| // ASCII-7 characters, these are the same. |
| final String p = getPersonal(); |
| if (p == null) { |
| return formatAddress(address); |
| } |
| else { |
| final StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3); |
| buf.append(AddressParser.quoteString(p)); |
| buf.append(" <").append(address).append(">"); |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * Compares two addresses for equality. |
| * We define this as true if the other object is an InternetAddress |
| * and the two values returned by getAddress() are equal in a |
| * case-insensitive comparison. |
| * |
| * @param o the other object |
| * @return true if the addresses are the same |
| */ |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (!(o instanceof InternetAddress)) { |
| return false; |
| } |
| |
| final InternetAddress other = (InternetAddress) o; |
| final String myAddress = getAddress(); |
| return myAddress == null ? (other.getAddress() == null) : myAddress.equalsIgnoreCase(other.getAddress()); |
| } |
| |
| /** |
| * Return the hashCode for this address. |
| * We define this to be the hashCode of the address after conversion to lowercase. |
| * |
| * @return a hashCode for this address |
| */ |
| @Override |
| public int hashCode() { |
| return (address == null) ? 0 : address.toLowerCase().hashCode(); |
| } |
| |
| /** |
| * Return true is this address is an RFC822 group address in the format |
| * <code>phrase ":" [#mailbox] ";"</code>. |
| * We check this by using the presense of a ':' character in the address, and a |
| * ';' as the very last character. |
| * |
| * @return true is this address represents a group |
| */ |
| public boolean isGroup() { |
| if (address == null) { |
| return false; |
| } |
| |
| return address.endsWith(";") && address.indexOf(":") > 0; |
| } |
| |
| /** |
| * Return the members of a group address. |
| * |
| * If strict is true and the address does not contain an initial phrase then an AddressException is thrown. |
| * Otherwise the phrase is skipped and the remainder of the address is checked to see if it is a group. |
| * If it is, the content and strict flag are passed to parseHeader to extract the list of addresses; |
| * if it is not a group then null is returned. |
| * |
| * @param strict whether strict RFC822 checking should be performed |
| * @return an array of InternetAddress objects for the group members, or null if this address is not a group |
| * @throws AddressException if there was a problem parsing the header |
| */ |
| public InternetAddress[] getGroup(final boolean strict) throws AddressException { |
| if (address == null) { |
| return null; |
| } |
| |
| // create an address parser and use it to extract the group information. |
| final AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT); |
| return parser.extractGroupList(); |
| } |
| |
| /** |
| * Return an InternetAddress representing the current user. |
| * <P/> |
| * If session is not null, we first look for an address specified in its |
| * "mail.from" property; if this is not set, we look at its "mail.user" |
| * and "mail.host" properties and if both are not null then an address of |
| * the form "${mail.user}@${mail.host}" is created. |
| * If this fails to give an address, then an attempt is made to create |
| * an address by combining the value of the "user.name" System property |
| * with the value returned from InetAddress.getLocalHost().getHostName(). |
| * Any SecurityException raised accessing the system property or any |
| * UnknownHostException raised getting the hostname are ignored. |
| * <P/> |
| * Finally, an attempt is made to convert the value obtained above to |
| * an InternetAddress. If this fails, then null is returned. |
| * |
| * @param session used to obtain mail properties |
| * @return an InternetAddress for the current user, or null if it cannot be determined |
| */ |
| public static InternetAddress getLocalAddress(final Session session) { |
| String host = null; |
| String user = null; |
| |
| // ok, we have several steps for resolving this. To start with, we could have a from address |
| // configured already, which will be a full InternetAddress string. If we don't have that, then |
| // we need to resolve a user and host to compose an address from. |
| if (session != null) { |
| final String address = session.getProperty("mail.from"); |
| // if we got this, we can skip out now |
| if (address != null) { |
| try { |
| return new InternetAddress(address); |
| } catch (final AddressException e) { |
| // invalid address on the from...treat this as an error and return null. |
| return null; |
| } |
| } |
| |
| // now try for user and host information. We have both session and system properties to check here. |
| // we'll just handle the session ones here, and check the system ones below if we're missing information. |
| user = session.getProperty("mail.user"); |
| host = session.getProperty("mail.host"); |
| } |
| |
| try { |
| |
| // if either user or host is null, then we check non-session sources for the information. |
| if (user == null) { |
| user = System.getProperty("user.name"); |
| } |
| |
| if (host == null) { |
| host = InetAddress.getLocalHost().getHostName(); |
| } |
| |
| if (user != null && host != null) { |
| // if we have both a user and host, we can create a local address |
| return new InternetAddress(user + '@' + host); |
| } |
| } catch (final AddressException e) { |
| // ignore |
| } catch (final UnknownHostException e) { |
| // ignore |
| } catch (final SecurityException e) { |
| // ignore |
| } |
| return null; |
| } |
| |
| /** |
| * Convert the supplied addresses into a single String of comma-separated text as |
| * produced by {@link InternetAddress#toString() toString()}. |
| * No line-break detection is performed. |
| * |
| * @param addresses the array of addresses to convert |
| * @return a one-line String of comma-separated addresses |
| */ |
| public static String toString(final Address[] addresses) { |
| if (addresses == null || addresses.length == 0) { |
| return null; |
| } |
| if (addresses.length == 1) { |
| return addresses[0].toString(); |
| } else { |
| final StringBuffer buf = new StringBuffer(addresses.length * 32); |
| buf.append(addresses[0].toString()); |
| for (int i = 1; i < addresses.length; i++) { |
| buf.append(", "); |
| buf.append(addresses[i].toString()); |
| } |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * Convert the supplies addresses into a String of comma-separated text, |
| * inserting line-breaks between addresses as needed to restrict the line |
| * length to 72 characters. Splits will only be introduced between addresses |
| * so an address longer than 71 characters will still be placed on a single |
| * line. |
| * |
| * @param addresses the array of addresses to convert |
| * @param used the starting column |
| * @return a String of comma-separated addresses with optional line breaks |
| */ |
| public static String toString(final Address[] addresses, int used) { |
| if (addresses == null || addresses.length == 0) { |
| return null; |
| } |
| if (addresses.length == 1) { |
| String s = addresses[0].toString(); |
| if (used + s.length() > 72) { |
| s = "\r\n " + s; |
| } |
| return s; |
| } else { |
| final StringBuffer buf = new StringBuffer(addresses.length * 32); |
| for (int i = 0; i < addresses.length; i++) { |
| final String s = addresses[1].toString(); |
| if (i == 0) { |
| if (used + s.length() + 1 > 72) { |
| buf.append("\r\n "); |
| used = 2; |
| } |
| } else { |
| if (used + s.length() + 1 > 72) { |
| buf.append(",\r\n "); |
| used = 2; |
| } else { |
| buf.append(", "); |
| used += 2; |
| } |
| } |
| buf.append(s); |
| used += s.length(); |
| } |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * Parse addresses out of the string with basic checking. |
| * |
| * @param addresses the addresses to parse |
| * @return an array of InternetAddresses parsed from the string |
| * @throws AddressException if addresses checking fails |
| */ |
| public static InternetAddress[] parse(final String addresses) throws AddressException { |
| return parse(addresses, true); |
| } |
| |
| /** |
| * Parse addresses out of the string. |
| * |
| * @param addresses the addresses to parse |
| * @param strict if true perform detailed checking, if false just perform basic checking |
| * @return an array of InternetAddresses parsed from the string |
| * @throws AddressException if address checking fails |
| */ |
| public static InternetAddress[] parse(final String addresses, final boolean strict) throws AddressException { |
| return parse(addresses, strict ? AddressParser.STRICT : AddressParser.NONSTRICT); |
| } |
| |
| /** |
| * Parse addresses out of the string. |
| * |
| * @param addresses the addresses to parse |
| * @param strict if true perform detailed checking, if false perform little checking |
| * @return an array of InternetAddresses parsed from the string |
| * @throws AddressException if address checking fails |
| */ |
| public static InternetAddress[] parseHeader(final String addresses, final boolean strict) throws AddressException { |
| return parse(addresses, strict ? AddressParser.STRICT : AddressParser.PARSE_HEADER); |
| } |
| |
| /** |
| * Parse addresses with increasing degrees of RFC822 compliance checking. |
| * |
| * @param addresses the string to parse |
| * @param level The required strictness level. |
| * |
| * @return an array of InternetAddresses parsed from the string |
| * @throws AddressException |
| * if address checking fails |
| */ |
| private static InternetAddress[] parse(final String addresses, final int level) throws AddressException { |
| // create a parser and have it extract the list using the requested strictness leve. |
| final AddressParser parser = new AddressParser(addresses, level); |
| return parser.parseAddressList(); |
| } |
| |
| /** |
| * Validate the address portion of an internet address to ensure |
| * validity. Throws an AddressException if any validity |
| * problems are encountered. |
| * |
| * @exception AddressException |
| */ |
| public void validate() throws AddressException { |
| |
| // create a parser using the strictest validation level. |
| final AddressParser parser = new AddressParser(formatAddress(address), AddressParser.STRICT); |
| parser.validateAddress(); |
| } |
| } |