You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by rm...@apache.org on 2014/08/26 20:17:09 UTC
svn commit: r1620683 [6/17] - in /geronimo/specs/trunk: ./
geronimo-javamail_1.5_spec/ geronimo-javamail_1.5_spec/src/
geronimo-javamail_1.5_spec/src/main/
geronimo-javamail_1.5_spec/src/main/java/
geronimo-javamail_1.5_spec/src/main/java/javax/ geroni...
Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/HeaderTokenizer.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/HeaderTokenizer.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/HeaderTokenizer.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/HeaderTokenizer.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,352 @@
+/*
+ * 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;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class HeaderTokenizer {
+ public static class Token {
+ // Constant values from J2SE 1.4 API Docs (Constant values)
+ public static final int ATOM = -1;
+ public static final int COMMENT = -3;
+ public static final int EOF = -4;
+ public static final int QUOTEDSTRING = -2;
+ private final int _type;
+ private final String _value;
+
+ public Token(final int type, final String value) {
+ _type = type;
+ _value = value;
+ }
+
+ public int getType() {
+ return _type;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+ }
+
+ private static final char NUL = '\0';
+ private static final Token EOF = new Token(Token.EOF, null);
+ // characters not allowed in MIME
+ public static final String MIME = "()<>@,;:\\\"\t []/?=";
+ // characters not allowed in RFC822
+ public static final String RFC822 = "()<>@,;:\\\"\t .[]";
+ private static final String WHITE = " \t\n\r";
+ private final String _delimiters;
+ private final String _header;
+ private final int _headerLength;
+ private final boolean _skip;
+ private int pos;
+
+ public HeaderTokenizer(final String header) {
+ this(header, RFC822);
+ }
+
+ public HeaderTokenizer(final String header, final String delimiters) {
+ this(header, delimiters, true);
+ }
+
+ public HeaderTokenizer(final String header,
+ final String delimiters,
+ final boolean skipComments) {
+ _skip = skipComments;
+ _header = header;
+ _delimiters = delimiters;
+ _headerLength=header.length();
+ }
+
+ //Return the rest of the Header.
+ //null is returned if we are already at end of header
+ public String getRemainder() {
+
+ if(pos > _headerLength) {
+ return null;
+ }
+
+ return _header.substring(pos);
+ }
+
+ public Token next() throws ParseException {
+ return readToken(NUL, false);
+ }
+
+ /**
+ * Parses the next token from this String.
+ * If endOfAtom is not NUL, the token extends until the
+ * endOfAtom character is seen, or to the end of the header.
+ * This method is useful when parsing headers that don't
+ * obey the MIME specification, e.g., by failing to quote
+ * parameter values that contain spaces.
+ *
+ * @param endOfAtom if not NUL, character marking end of token
+ * @return the next Token
+ * @exception ParseException if the parse fails
+ * @since JavaMail 1.5
+ */
+ public Token next(final char endOfAtom) throws ParseException {
+ return next(endOfAtom, false);
+ }
+
+ /**
+ * Parses the next token from this String.
+ * endOfAtom is handled as above. If keepEscapes is true,
+ * any backslash escapes are preserved in the returned string.
+ * This method is useful when parsing headers that don't
+ * obey the MIME specification, e.g., by failing to escape
+ * backslashes in the filename parameter.
+ *
+ * @param endOfAtom if not NUL, character marking end of token
+ * @param keepEscapes keep all backslashes in returned string?
+ * @return the next Token
+ * @exception ParseException if the parse fails
+ * @since JavaMail 1.5
+ */
+ public Token next(final char endOfAtom, final boolean keepEscapes)
+ throws ParseException {
+ return readToken(endOfAtom, keepEscapes);
+ }
+
+
+ public Token peek() throws ParseException {
+ final int start = pos;
+ try {
+ return readToken(NUL, false);
+ } finally {
+ pos = start;
+ }
+ }
+
+ /**
+ * Read an ATOM token from the parsed header.
+ *
+ * @return A token containing the value of the atom token.
+ */
+ private Token readAtomicToken() {
+ // skip to next delimiter
+ final int start = pos;
+ final StringBuilder sb = new StringBuilder();
+ sb.append(_header.charAt(pos));
+ while (++pos < _headerLength) {
+ // break on the first non-atom character.
+ final char ch = _header.charAt(pos);
+
+ if ((_delimiters.indexOf(_header.charAt(pos)) != -1 || ch < 32 || ch >= 127)) {
+ break;
+ }
+ }
+
+ return new Token(Token.ATOM, _header.substring(start, pos));
+ }
+
+ /**
+ * Read the next token from the header.
+ *
+ * @return The next token from the header. White space is skipped, and comment
+ * tokens are also skipped if indicated.
+ * @exception ParseException
+ */
+ private Token readToken(final char endOfAtom, final boolean keepEscapes) throws ParseException {
+ if (pos >= _headerLength) {
+ return EOF;
+ } else {
+ final char c = _header.charAt(pos);
+ // comment token...read and skip over this
+ if (c == '(') {
+ final Token comment = readComment(keepEscapes);
+ if (_skip) {
+ return readToken(endOfAtom, keepEscapes);
+ } else {
+ return comment;
+ }
+ // quoted literal
+ } else if (c == '\"') {
+ return readQuotedString('"', keepEscapes, 1);
+ // white space, eat this and find a real token.
+ } else if (WHITE.indexOf(c) != -1) {
+ eatWhiteSpace();
+ return readToken(endOfAtom, keepEscapes);
+ // either a CTL or special. These characters have a self-defining token type.
+ } else if (c < 32 || c >= 127 || _delimiters.indexOf(c) != -1) {
+
+ if (endOfAtom != NUL && c != endOfAtom) {
+ return readQuotedString(endOfAtom, keepEscapes, 0);
+ }
+
+
+ pos++;
+ return new Token(c, String.valueOf(c));
+ } else {
+ // start of an atom, parse it off.
+ if (endOfAtom != NUL && c != endOfAtom) {
+ return readQuotedString(endOfAtom, keepEscapes, 0);
+ }
+
+ return readAtomicToken();
+ }
+ }
+ }
+
+ /**
+ * Extract a substring from the header string and apply any
+ * escaping/folding rules to the string.
+ *
+ * @param start The starting offset in the header.
+ * @param end The header end offset + 1.
+ *
+ * @return The processed string value.
+ * @exception ParseException
+ */
+ private String getEscapedValue(final int start, final int end, final boolean keepEscapes) throws ParseException {
+ final StringBuffer value = new StringBuffer();
+
+ for (int i = start; i < end; i++) {
+ final char ch = _header.charAt(i);
+ // is this an escape character?
+ if (ch == '\\') {
+ i++;
+ if (i == end) {
+ throw new ParseException("Invalid escape character");
+ }
+
+ if(keepEscapes) {
+ value.append("\\");
+ }
+
+ value.append(_header.charAt(i));
+ }
+ // line breaks are ignored, except for naked '\n' characters, which are consider
+ // parts of linear whitespace.
+ else if (ch == '\r') {
+ // see if this is a CRLF sequence, and skip the second if it is.
+ if (i < end - 1 && _header.charAt(i + 1) == '\n') {
+ i++;
+ }
+ }
+ else {
+
+ // just append the ch value.
+ value.append(ch);
+ }
+ }
+ return value.toString();
+ }
+
+ /**
+ * Read a comment from the header, applying nesting and escape
+ * rules to the content.
+ *
+ * @return A comment token with the token value.
+ * @exception ParseException
+ */
+ private Token readComment(final boolean keepEscapes) throws ParseException {
+ final int start = pos + 1;
+ int nesting = 1;
+
+ boolean requiresEscaping = false;
+
+ // skip to end of comment/string
+ while (++pos < _headerLength) {
+ final char ch = _header.charAt(pos);
+ if (ch == ')') {
+ nesting--;
+ if (nesting == 0) {
+ break;
+ }
+ }
+ else if (ch == '(') {
+ nesting++;
+ }
+ else if (ch == '\\') {
+ pos++;
+ requiresEscaping = true;
+ }
+ // we need to process line breaks also
+ else if (ch == '\r') {
+ requiresEscaping = true;
+ }
+ }
+
+ if (nesting != 0) {
+ throw new ParseException("Unbalanced comments");
+ }
+
+ String value;
+ if (requiresEscaping) {
+ value = getEscapedValue(start, pos, keepEscapes);
+ }
+ else {
+ value = _header.substring(start, pos++);
+ }
+ return new Token(Token.COMMENT, value);
+ }
+
+ /**
+ * Parse out a quoted string from the header, applying escaping
+ * rules to the value.
+ *
+ * @return The QUOTEDSTRING token with the value.
+ * @exception ParseException
+ */
+ private Token readQuotedString(final char endChar, final boolean keepEscapes, final int offset) throws ParseException {
+ final int start = pos+offset;
+ boolean requiresEscaping = false;
+
+ // skip to end of comment/string
+ while (++pos < _headerLength) {
+ final char ch = _header.charAt(pos);
+
+ if (ch == endChar) {
+ String value;
+ if (requiresEscaping) {
+ value = getEscapedValue(start, pos++, keepEscapes);
+ }
+ else {
+ value = _header.substring(start, pos++);
+ }
+ return new Token(Token.QUOTEDSTRING, value);
+ }
+ else if (ch == '\\') {
+ pos++;
+ requiresEscaping = true;
+ }
+ // we need to process line breaks also
+ else if (ch == '\r') {
+ requiresEscaping = true;
+ }
+ }
+
+ throw new ParseException("Missing '\"'");
+ }
+
+ /**
+ * Skip white space in the token string.
+ */
+ private void eatWhiteSpace() {
+ // skip to end of whitespace
+ while (++pos < _headerLength
+ && WHITE.indexOf(_header.charAt(pos)) != -1) {
+ ;
+ }
+ }
+}
Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetAddress.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetAddress.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetAddress.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetAddress.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,576 @@
+/*
+ * 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();
+ }
+}
Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetHeaders.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetHeaders.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetHeaders.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/InternetHeaders.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,716 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.mail.Address;
+import javax.mail.Header;
+import javax.mail.MessagingException;
+
+/**
+ * Class that represents the RFC822 headers associated with a message.
+ *
+ * @version $Rev$ $Date$
+ */
+public class InternetHeaders {
+ // the list of headers (to preserve order);
+ protected List headers = new ArrayList();
+
+ /**
+ * Create an empty InternetHeaders
+ */
+ public InternetHeaders() {
+ // these are created in the preferred order of the headers.
+ addHeader("Return-Path", null);
+ addHeader("Received", null);
+ addHeader("Resent-Date", null);
+ addHeader("Resent-From", null);
+ addHeader("Resent-Sender", null);
+ addHeader("Resent-To", null);
+ addHeader("Resent-Cc", null);
+ addHeader("Resent-Bcc", null);
+ addHeader("Resent-Message-Id", null);
+ addHeader("Date", null);
+ addHeader("From", null);
+ addHeader("Sender", null);
+ addHeader("Reply-To", null);
+ addHeader("To", null);
+ addHeader("Cc", null);
+ addHeader("Bcc", null);
+ addHeader("Message-Id", null);
+ addHeader("In-Reply-To", null);
+ addHeader("References", null);
+ addHeader("Subject", null);
+ addHeader("Comments", null);
+ addHeader("Keywords", null);
+ addHeader("Errors-To", null);
+ addHeader("MIME-Version", null);
+ addHeader("Content-Type", null);
+ addHeader("Content-Transfer-Encoding", null);
+ addHeader("Content-MD5", null);
+ // the following is a special marker used to identify new header insertion points.
+ addHeader(":", null);
+ addHeader("Content-Length", null);
+ addHeader("Status", null);
+ }
+
+ /**
+ * Create a new InternetHeaders initialized by reading headers from the
+ * stream.
+ *
+ * @param in
+ * the RFC822 input stream to load from
+ * @throws MessagingException
+ * if there is a problem pasring the stream
+ */
+ public InternetHeaders(final InputStream in) throws MessagingException {
+ load(in);
+ }
+
+ /**
+ * Read and parse the supplied stream and add all headers to the current
+ * set.
+ *
+ * @param in
+ * the RFC822 input stream to load from
+ * @throws MessagingException
+ * if there is a problem pasring the stream
+ */
+ public void load(final InputStream in) throws MessagingException {
+ try {
+ final StringBuffer buffer = new StringBuffer(128);
+ String line;
+ // loop until we hit the end or a null line
+ while ((line = readLine(in)) != null) {
+ // lines beginning with white space get special handling
+ if (line.startsWith(" ") || line.startsWith("\t")) {
+ // this gets handled using the logic defined by
+ // the addHeaderLine method. If this line is a continuation, but
+ // there's nothing before it, just call addHeaderLine to add it
+ // to the last header in the headers list
+ if (buffer.length() == 0) {
+ addHeaderLine(line);
+ }
+ else {
+ // preserve the line break and append the continuation
+ buffer.append("\r\n");
+ buffer.append(line);
+ }
+ }
+ else {
+ // if we have a line pending in the buffer, flush it
+ if (buffer.length() > 0) {
+ addHeaderLine(buffer.toString());
+ buffer.setLength(0);
+ }
+ // add this to the accumulator
+ buffer.append(line);
+ }
+ }
+
+ // if we have a line pending in the buffer, flush it
+ if (buffer.length() > 0) {
+ addHeaderLine(buffer.toString());
+ }
+ } catch (final IOException e) {
+ throw new MessagingException("Error loading headers", e);
+ }
+ }
+
+
+ /**
+ * Read a single line from the input stream
+ *
+ * @param in The source stream for the line
+ *
+ * @return The string value of the line (without line separators)
+ */
+ private String readLine(final InputStream in) throws IOException {
+ final StringBuffer buffer = new StringBuffer(128);
+
+ int c;
+
+ while ((c = in.read()) != -1) {
+ // a linefeed is a terminator, always.
+ if (c == '\n') {
+ break;
+ }
+ // just ignore the CR. The next character SHOULD be an NL. If not, we're
+ // just going to discard this
+ else if (c == '\r') {
+ continue;
+ }
+ else {
+ // just add to the buffer
+ buffer.append((char)c);
+ }
+ }
+
+ // no characters found...this was either an eof or a null line.
+ if (buffer.length() == 0) {
+ return null;
+ }
+
+ return buffer.toString();
+ }
+
+
+ /**
+ * Return all the values for the specified header.
+ *
+ * @param name
+ * the header to return
+ * @return the values for that header, or null if the header is not present
+ */
+ public String[] getHeader(final String name) {
+ final List accumulator = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ if (header.getName().equalsIgnoreCase(name) && header.getValue() != null) {
+ accumulator.add(header.getValue());
+ }
+ }
+
+ // this is defined as returning null of nothing is found.
+ if (accumulator.isEmpty()) {
+ return null;
+ }
+
+ // convert this to an array.
+ return (String[])accumulator.toArray(new String[accumulator.size()]);
+ }
+
+ /**
+ * Return the values for the specified header as a single String. If the
+ * header has more than one value then all values are concatenated together
+ * separated by the supplied delimiter.
+ *
+ * @param name
+ * the header to return
+ * @param delimiter
+ * the delimiter used in concatenation
+ * @return the header as a single String
+ */
+ public String getHeader(final String name, final String delimiter) {
+ // get all of the headers with this name
+ final String[] matches = getHeader(name);
+
+ // no match? return a null.
+ if (matches == null) {
+ return null;
+ }
+
+ // a null delimiter means just return the first one. If there's only one item, this is easy too.
+ if (matches.length == 1 || delimiter == null) {
+ return matches[0];
+ }
+
+ // perform the concatenation
+ final StringBuffer result = new StringBuffer(matches[0]);
+
+ for (int i = 1; i < matches.length; i++) {
+ result.append(delimiter);
+ result.append(matches[i]);
+ }
+
+ return result.toString();
+ }
+
+
+ /**
+ * Set the value of the header to the supplied value; any existing headers
+ * are removed.
+ *
+ * @param name
+ * the name of the header
+ * @param value
+ * the new value
+ */
+ public void setHeader(final String name, final String value) {
+ // look for a header match
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // found a matching header
+ if (name.equalsIgnoreCase(header.getName())) {
+ // we update both the name and the value for a set so that
+ // the header ends up with the same case as what is getting set
+ header.setValue(value);
+ header.setName(name);
+ // remove all of the headers from this point
+ removeHeaders(name, i + 1);
+ return;
+ }
+ }
+
+ // doesn't exist, so process as an add.
+ addHeader(name, value);
+ }
+
+
+ /**
+ * Remove all headers with the given name, starting with the
+ * specified start position.
+ *
+ * @param name The target header name.
+ * @param pos The position of the first header to examine.
+ */
+ private void removeHeaders(final String name, final int pos) {
+ // now go remove all other instances of this header
+ for (int i = pos; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // found a matching header
+ if (name.equalsIgnoreCase(header.getName())) {
+ // remove this item, and back up
+ headers.remove(i);
+ i--;
+ }
+ }
+ }
+
+
+ /**
+ * Find a header in the current list by name, returning the index.
+ *
+ * @param name The target name.
+ *
+ * @return The index of the header in the list. Returns -1 for a not found
+ * condition.
+ */
+ private int findHeader(final String name) {
+ return findHeader(name, 0);
+ }
+
+
+ /**
+ * Find a header in the current list, beginning with the specified
+ * start index.
+ *
+ * @param name The target header name.
+ * @param start The search start index.
+ *
+ * @return The index of the first matching header. Returns -1 if the
+ * header is not located.
+ */
+ private int findHeader(final String name, final int start) {
+ for (int i = start; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // found a matching header
+ if (name.equalsIgnoreCase(header.getName())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Add a new value to the header with the supplied name.
+ *
+ * @param name
+ * the name of the header to add a new value for
+ * @param value
+ * another value
+ */
+ public void addHeader(final String name, final String value) {
+ final InternetHeader newHeader = new InternetHeader(name, value);
+
+ // The javamail spec states that "Recieved" headers need to be added in reverse order.
+ // Return-Path is permitted before Received, so handle it the same way.
+ if (name.equalsIgnoreCase("Received") || name.equalsIgnoreCase("Return-Path")) {
+ // see if we have one of these already
+ final int pos = findHeader(name);
+
+ // either insert before an existing header, or insert at the very beginning
+ if (pos != -1) {
+ // this could be a placeholder header with a null value. If it is, just update
+ // the value. Otherwise, insert in front of the existing header.
+ final InternetHeader oldHeader = (InternetHeader)headers.get(pos);
+ if (oldHeader.getValue() == null) {
+ oldHeader.setValue(value);
+ }
+ else {
+ headers.add(pos, newHeader);
+ }
+ }
+ else {
+ // doesn't exist, so insert at the beginning
+ headers.add(0, newHeader);
+ }
+ }
+ // normal insertion
+ else {
+ // see if we have one of these already
+ int pos = findHeader(name);
+
+ // either insert before an existing header, or insert at the very beginning
+ if (pos != -1) {
+ final InternetHeader oldHeader = (InternetHeader)headers.get(pos);
+ // if the existing header is a place holder, we can just update the value
+ if (oldHeader.getValue() == null) {
+ oldHeader.setValue(value);
+ }
+ else {
+ // we have at least one existing header with this name. We need to find the last occurrance,
+ // and insert after that spot.
+
+ int lastPos = findHeader(name, pos + 1);
+
+ while (lastPos != -1) {
+ pos = lastPos;
+ lastPos = findHeader(name, pos + 1);
+ }
+
+ // ok, we have the insertion position
+ headers.add(pos + 1, newHeader);
+ }
+ }
+ else {
+ // find the insertion marker. If that is missing somehow, insert at the end.
+ pos = findHeader(":");
+ if (pos == -1) {
+ pos = headers.size();
+ }
+ headers.add(pos, newHeader);
+ }
+ }
+ }
+
+
+ /**
+ * Remove all header entries with the supplied name
+ *
+ * @param name
+ * the header to remove
+ */
+ public void removeHeader(final String name) {
+ // the first occurrance of a header is just zeroed out.
+ final int pos = findHeader(name);
+
+ if (pos != -1) {
+ final InternetHeader oldHeader = (InternetHeader)headers.get(pos);
+ // keep the header in the list, but with a null value
+ oldHeader.setValue(null);
+ // now remove all other headers with this name
+ removeHeaders(name, pos + 1);
+ }
+ }
+
+
+ /**
+ * Return all headers.
+ *
+ * @return an Enumeration<Header> containing all headers
+ */
+ public Enumeration getAllHeaders() {
+ final List result = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ result.add(header);
+ }
+ }
+ // just return a list enumerator for the header list.
+ return Collections.enumeration(result);
+ }
+
+
+ /**
+ * Test if a given header name is a match for any header in the
+ * given list.
+ *
+ * @param name The name of the current tested header.
+ * @param names The list of names to match against.
+ *
+ * @return True if this is a match for any name in the list, false
+ * for a complete mismatch.
+ */
+ private boolean matchHeader(final String name, final String[] names) {
+ // the list of names is not required, so treat this as if it
+ // was an empty list and we didn't get a match.
+ if (names == null) {
+ return false;
+ }
+
+ for (int i = 0; i < names.length; i++) {
+ if (name.equalsIgnoreCase(names[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Return all matching Header objects.
+ */
+ public Enumeration getMatchingHeaders(final String[] names) {
+ final List result = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ // only add the matching ones
+ if (matchHeader(header.getName(), names)) {
+ result.add(header);
+ }
+ }
+ }
+ return Collections.enumeration(result);
+ }
+
+
+ /**
+ * Return all non matching Header objects.
+ */
+ public Enumeration getNonMatchingHeaders(final String[] names) {
+ final List result = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ // only add the non-matching ones
+ if (!matchHeader(header.getName(), names)) {
+ result.add(header);
+ }
+ }
+ }
+ return Collections.enumeration(result);
+ }
+
+
+ /**
+ * Add an RFC822 header line to the header store. If the line starts with a
+ * space or tab (a continuation line), add it to the last header line in the
+ * list. Otherwise, append the new header line to the list.
+ *
+ * Note that RFC822 headers can only contain US-ASCII characters
+ *
+ * @param line
+ * raw RFC822 header line
+ */
+ public void addHeaderLine(final String line) {
+ // null lines are a nop
+ if (line.length() == 0) {
+ return;
+ }
+
+ // we need to test the first character to see if this is a continuation whitespace
+ final char ch = line.charAt(0);
+
+ // tabs and spaces are special. This is a continuation of the last header in the list.
+ if (ch == ' ' || ch == '\t') {
+ final int size = headers.size();
+ // it's possible that we have a leading blank line.
+ if (size > 0) {
+ final InternetHeader header = (InternetHeader)headers.get(size - 1);
+ header.appendValue(line);
+ }
+ }
+ else {
+ // this just gets appended to the end, preserving the addition order.
+ headers.add(new InternetHeader(line));
+ }
+ }
+
+
+ /**
+ * Return all the header lines as an Enumeration of Strings.
+ */
+ public Enumeration getAllHeaderLines() {
+ return new HeaderLineEnumeration(getAllHeaders());
+ }
+
+ /**
+ * Return all matching header lines as an Enumeration of Strings.
+ */
+ public Enumeration getMatchingHeaderLines(final String[] names) {
+ return new HeaderLineEnumeration(getMatchingHeaders(names));
+ }
+
+ /**
+ * Return all non-matching header lines.
+ */
+ public Enumeration getNonMatchingHeaderLines(final String[] names) {
+ return new HeaderLineEnumeration(getNonMatchingHeaders(names));
+ }
+
+
+ /**
+ * Set an internet header from a list of addresses. The
+ * first address item is set, followed by a series of addHeaders().
+ *
+ * @param name The name to set.
+ * @param addresses The list of addresses to set.
+ */
+ void setHeader(final String name, final Address[] addresses) {
+ // if this is empty, then we need to replace this
+ if (addresses.length == 0) {
+ removeHeader(name);
+ } else {
+
+ // replace the first header
+ setHeader(name, addresses[0].toString());
+
+ // now add the rest as extra headers.
+ for (int i = 1; i < addresses.length; i++) {
+ final Address address = addresses[i];
+ addHeader(name, address.toString());
+ }
+ }
+ }
+
+
+ /**
+ * Write out the set of headers, except for any
+ * headers specified in the optional ignore list.
+ *
+ * @param out The output stream.
+ * @param ignore The optional ignore list.
+ *
+ * @exception IOException
+ */
+ void writeTo(final OutputStream out, final String[] ignore) throws IOException {
+ if (ignore == null) {
+ // write out all header lines with non-null values
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ header.writeTo(out);
+ }
+ }
+ }
+ else {
+ // write out all matching header lines with non-null values
+ for (int i = 0; i < headers.size(); i++) {
+ final InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ if (!matchHeader(header.getName(), ignore)) {
+ header.writeTo(out);
+ }
+ }
+ }
+ }
+ }
+
+ protected static final class InternetHeader extends Header {
+
+ public InternetHeader(final String h) {
+ // initialize with null values, which we'll update once we parse the string
+ super("", "");
+ int separator = h.indexOf(':');
+ // no separator, then we take this as a name with a null string value.
+ if (separator == -1) {
+ name = h.trim();
+ }
+ else {
+ name = h.substring(0, separator);
+ // step past the separator. Now we need to remove any leading white space characters.
+ separator++;
+
+ while (separator < h.length()) {
+ final char ch = h.charAt(separator);
+ if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
+ break;
+ }
+ separator++;
+ }
+
+ value = h.substring(separator);
+ }
+ }
+
+ public InternetHeader(final String name, final String value) {
+ super(name, value);
+ }
+
+
+ /**
+ * Package scope method for setting the header value.
+ *
+ * @param value The new header value.
+ */
+ void setValue(final String value) {
+ this.value = value;
+ }
+
+
+ /**
+ * Package scope method for setting the name value.
+ *
+ * @param name The new header name
+ */
+ void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Package scope method for extending a header value.
+ *
+ * @param value The appended header value.
+ */
+ void appendValue(final String value) {
+ if (this.value == null) {
+ this.value = value;
+ }
+ else {
+ this.value = this.value + "\r\n" + value;
+ }
+ }
+
+ void writeTo(final OutputStream out) throws IOException {
+ out.write(name.getBytes("ISO8859-1"));
+ out.write(':');
+ out.write(' ');
+ out.write(value.getBytes("ISO8859-1"));
+ out.write('\r');
+ out.write('\n');
+ }
+ }
+
+ private static class HeaderLineEnumeration implements Enumeration {
+ private final Enumeration headers;
+
+ public HeaderLineEnumeration(final Enumeration headers) {
+ this.headers = headers;
+ }
+
+ public boolean hasMoreElements() {
+ return headers.hasMoreElements();
+ }
+
+ public Object nextElement() {
+ final Header h = (Header) headers.nextElement();
+ return h.getName() + ": " + h.getValue();
+ }
+ }
+}
Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MailDateFormat.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MailDateFormat.java?rev=1620683&view=auto
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MailDateFormat.java (added)
+++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/internet/MailDateFormat.java Tue Aug 26 18:17:06 2014
@@ -0,0 +1,618 @@
+/*
+ * 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.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Formats ths date as specified by
+ * draft-ietf-drums-msg-fmt-08 dated January 26, 2000
+ * which supercedes RFC822.
+ * <p/>
+ * <p/>
+ * The format used is <code>EEE, d MMM yyyy HH:mm:ss Z</code> and
+ * locale is always US-ASCII.
+ *
+ * @version $Rev$ $Date$
+ */
+public class MailDateFormat extends SimpleDateFormat {
+
+ private static final long serialVersionUID = -8148227605210628779L;
+
+ public MailDateFormat() {
+ super("EEE, d MMM yyyy HH:mm:ss Z (z)", Locale.US);
+ }
+
+ @Override
+ public StringBuffer format(final Date date, final StringBuffer buffer, final FieldPosition position) {
+ return super.format(date, buffer, position);
+ }
+
+ /**
+ * Parse a Mail date into a Date object. This uses fairly
+ * lenient rules for the format because the Mail standards
+ * for dates accept multiple formats.
+ *
+ * @param string The input string.
+ * @param position The position argument.
+ *
+ * @return The Date object with the information inside.
+ */
+ @Override
+ public Date parse(final String string, final ParsePosition position) {
+ final MailDateParser parser = new MailDateParser(string, position);
+ try {
+ return parser.parse(isLenient());
+ } catch (final ParseException e) {
+ e.printStackTrace();
+ // just return a null for any parsing errors
+ return null;
+ }
+ }
+
+ /**
+ * The calendar cannot be set
+ * @param calendar
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void setCalendar(final Calendar calendar) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * The format cannot be set
+ * @param format
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void setNumberFormat(final NumberFormat format) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ // utility class for handling date parsing issues
+ class MailDateParser {
+ // our list of defined whitespace characters
+ static final String whitespace = " \t\r\n";
+
+ // current parsing position
+ int current;
+ // our end parsing position
+ int endOffset;
+ // the date source string
+ String source;
+ // The parsing position. We update this as we move along and
+ // also for any parsing errors
+ ParsePosition pos;
+
+ public MailDateParser(final String source, final ParsePosition pos)
+ {
+ this.source = source;
+ this.pos = pos;
+ // we start using the providing parsing index.
+ this.current = pos.getIndex();
+ this.endOffset = source.length();
+ }
+
+ /**
+ * Parse the timestamp, returning a date object.
+ *
+ * @param lenient The lenient setting from the Formatter object.
+ *
+ * @return A Date object based off of parsing the date string.
+ * @exception ParseException
+ */
+ public Date parse(final boolean lenient) throws ParseException {
+ // we just skip over any next date format, which means scanning ahead until we
+ // find the first numeric character
+ locateNumeric();
+ // the day can be either 1 or two digits
+ final int day = parseNumber(1, 2);
+ // step over the delimiter
+ skipDateDelimiter();
+ // parse off the month (which is in character format)
+ final int month = parseMonth();
+ // step over the delimiter
+ skipDateDelimiter();
+ // now pull of the year, which can be either 2-digit or 4-digit
+ final int year = parseYear();
+ // white space is required here
+ skipRequiredWhiteSpace();
+ // accept a 1 or 2 digit hour
+ final int hour = parseNumber(1, 2);
+ skipRequiredChar(':');
+ // the minutes must be two digit
+ final int minutes = parseNumber(2, 2);
+
+ // the seconds are optional, but the ":" tells us if they are to
+ // be expected.
+ int seconds = 0;
+ if (skipOptionalChar(':')) {
+ seconds = parseNumber(2, 2);
+ }
+ // skip over the white space
+ skipWhiteSpace();
+ // and finally the timezone information
+ final int offset = parseTimeZone();
+
+ // set the index of how far we've parsed this
+ pos.setIndex(current);
+
+ // create a calendar for creating the date
+ final Calendar greg = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
+ // we inherit the leniency rules
+ greg.setLenient(lenient);
+ greg.set(year, month, day, hour, minutes, seconds);
+ // now adjust by the offset. This seems a little strange, but we
+ // need to negate the offset because this is a UTC calendar, so we need to
+ // apply the reverse adjustment. for example, for the EST timezone, the offset
+ // value will be -300 (5 hours). If the time was 15:00:00, the UTC adjusted time
+ // needs to be 20:00:00, so we subract -300 minutes.
+ greg.add(Calendar.MINUTE, -offset);
+ // now return this timestamp.
+ return greg.getTime();
+ }
+
+
+ /**
+ * Skip over a position where there's a required value
+ * expected.
+ *
+ * @param ch The required character.
+ *
+ * @exception ParseException
+ */
+ private void skipRequiredChar(final char ch) throws ParseException {
+ if (current >= endOffset) {
+ parseError("Delimiter '" + ch + "' expected");
+ }
+ if (source.charAt(current) != ch) {
+ parseError("Delimiter '" + ch + "' expected");
+ }
+ current++;
+ }
+
+
+ /**
+ * Skip over a position where iff the position matches the
+ * character
+ *
+ * @param ch The required character.
+ *
+ * @return true if the character was there, false otherwise.
+ * @exception ParseException
+ */
+ private boolean skipOptionalChar(final char ch) {
+ if (current >= endOffset) {
+ return false;
+ }
+ if (source.charAt(current) != ch) {
+ return false;
+ }
+ current++;
+ return true;
+ }
+
+
+ /**
+ * Skip over any white space characters until we find
+ * the next real bit of information. Will scan completely to the
+ * end, if necessary.
+ */
+ private void skipWhiteSpace() {
+ while (current < endOffset) {
+ // if this is not in the white space list, then success.
+ if (whitespace.indexOf(source.charAt(current)) < 0) {
+ return;
+ }
+ current++;
+ }
+
+ // everything used up, just return
+ }
+
+
+ /**
+ * Skip over any non-white space characters until we find
+ * either a whitespace char or the end of the data.
+ */
+ private void skipNonWhiteSpace() {
+ while (current < endOffset) {
+ // if this is not in the white space list, then success.
+ if (whitespace.indexOf(source.charAt(current)) >= 0) {
+ return;
+ }
+ current++;
+ }
+
+ // everything used up, just return
+ }
+
+
+ /**
+ * Skip over any white space characters until we find
+ * the next real bit of information. Will scan completely to the
+ * end, if necessary.
+ */
+ private void skipRequiredWhiteSpace() throws ParseException {
+ final int start = current;
+
+ while (current < endOffset) {
+ // if this is not in the white space list, then success.
+ if (whitespace.indexOf(source.charAt(current)) < 0) {
+ // we must have at least one white space character
+ if (start == current) {
+ parseError("White space character expected");
+ }
+ return;
+ }
+ current++;
+ }
+ // everything used up, just return, but make sure we had at least one
+ // white space
+ if (start == current) {
+ parseError("White space character expected");
+ }
+ }
+
+ private void parseError(final String message) throws ParseException {
+ // we've got an error, set the index to the end.
+ pos.setErrorIndex(current);
+ throw new ParseException(message, current);
+ }
+
+
+ /**
+ * Locate an expected numeric field.
+ *
+ * @exception ParseException
+ */
+ private void locateNumeric() throws ParseException {
+ while (current < endOffset) {
+ // found a digit? we're done
+ if (Character.isDigit(source.charAt(current))) {
+ return;
+ }
+ current++;
+ }
+ // we've got an error, set the index to the end.
+ parseError("Number field expected");
+ }
+
+
+ /**
+ * Parse out an expected numeric field.
+ *
+ * @param minDigits The minimum number of digits we expect in this filed.
+ * @param maxDigits The maximum number of digits expected. Parsing will
+ * stop at the first non-digit character. An exception will
+ * be thrown if the field contained more than maxDigits
+ * in it.
+ *
+ * @return The parsed numeric value.
+ * @exception ParseException
+ */
+ private int parseNumber(final int minDigits, final int maxDigits) throws ParseException {
+ final int start = current;
+ int accumulator = 0;
+ while (current < endOffset) {
+ final char ch = source.charAt(current);
+ // if this is not a digit character, then quit
+ if (!Character.isDigit(ch)) {
+ break;
+ }
+ // add the digit value into the accumulator
+ accumulator = accumulator * 10 + Character.digit(ch, 10);
+ current++;
+ }
+
+ final int fieldLength = current - start;
+ if (fieldLength < minDigits || fieldLength > maxDigits) {
+ parseError("Invalid number field");
+ }
+
+ return accumulator;
+ }
+
+ /**
+ * Skip a delimiter between the date portions of the
+ * string. The IMAP internal date format uses "-", so
+ * we either accept a single "-" or any number of white
+ * space characters (at least one required).
+ *
+ * @exception ParseException
+ */
+ private void skipDateDelimiter() throws ParseException {
+ if (current >= endOffset) {
+ parseError("Invalid date field delimiter");
+ }
+
+ if (source.charAt(current) == '-') {
+ current++;
+ }
+ else {
+ // must be at least a single whitespace character
+ skipRequiredWhiteSpace();
+ }
+ }
+
+
+ /**
+ * Parse a character month name into the date month
+ * offset.
+ *
+ * @return
+ * @exception ParseException
+ */
+ private int parseMonth() throws ParseException {
+ if ((endOffset - current) < 3) {
+ parseError("Invalid month");
+ }
+
+ int monthOffset = 0;
+ final String month = source.substring(current, current + 3).toLowerCase();
+
+ if (month.equals("jan")) {
+ monthOffset = 0;
+ }
+ else if (month.equals("feb")) {
+ monthOffset = 1;
+ }
+ else if (month.equals("mar")) {
+ monthOffset = 2;
+ }
+ else if (month.equals("apr")) {
+ monthOffset = 3;
+ }
+ else if (month.equals("may")) {
+ monthOffset = 4;
+ }
+ else if (month.equals("jun")) {
+ monthOffset = 5;
+ }
+ else if (month.equals("jul")) {
+ monthOffset = 6;
+ }
+ else if (month.equals("aug")) {
+ monthOffset = 7;
+ }
+ else if (month.equals("sep")) {
+ monthOffset = 8;
+ }
+ else if (month.equals("oct")) {
+ monthOffset = 9;
+ }
+ else if (month.equals("nov")) {
+ monthOffset = 10;
+ }
+ else if (month.equals("dec")) {
+ monthOffset = 11;
+ }
+ else {
+ parseError("Invalid month");
+ }
+
+ // ok, this is valid. Update the position and return it
+ current += 3;
+ return monthOffset;
+ }
+
+ /**
+ * Parse off a year field that might be expressed as
+ * either 2 or 4 digits.
+ *
+ * @return The numeric value of the year.
+ * @exception ParseException
+ */
+ private int parseYear() throws ParseException {
+ // the year is between 2 to 4 digits
+ int year = parseNumber(2, 4);
+
+ // the two digit years get some sort of adjustment attempted.
+ if (year < 50) {
+ year += 2000;
+ }
+ else if (year < 100) {
+ year += 1990;
+ }
+ return year;
+ }
+
+
+ /**
+ * Parse all of the different timezone options.
+ *
+ * @return The timezone offset.
+ * @exception ParseException
+ */
+ private int parseTimeZone() throws ParseException {
+ if (current >= endOffset) {
+ parseError("Missing time zone");
+ }
+
+ // get the first non-blank. If this is a sign character, this
+ // is a zone offset.
+ final char sign = source.charAt(current);
+
+ if (sign == '-' || sign == '+') {
+ // need to step over the sign character
+ current++;
+ // a numeric timezone is always a 4 digit number, but
+ // expressed as minutes/seconds. I'm too lazy to write a
+ // different parser that will bound on just a couple of characters, so
+ // we'll grab this as a single value and adjust
+ final int zoneInfo = parseNumber(4, 4);
+
+ int offset = (zoneInfo / 100) * 60 + (zoneInfo % 100);
+ // negate this, if we have a negativeo offset
+ if (sign == '-') {
+ offset = -offset;
+ }
+ return offset;
+ }
+ else {
+ // need to parse this out using the obsolete zone names. This will be
+ // either a 3-character code (defined set), or a single character military
+ // zone designation.
+ final int start = current;
+ skipNonWhiteSpace();
+ final String name = source.substring(start, current).toUpperCase();
+
+ if (name.length() == 1) {
+ return militaryZoneOffset(name);
+ }
+ else if (name.length() <= 3) {
+ return namedZoneOffset(name);
+ }
+ else {
+ parseError("Invalid time zone");
+ }
+ return 0;
+ }
+ }
+
+
+ /**
+ * Parse the obsolete mail timezone specifiers. The
+ * allowed set of timezones are terribly US centric.
+ * That's the spec. The preferred timezone form is
+ * the +/-mmss form.
+ *
+ * @param name The input name.
+ *
+ * @return The standard timezone offset for the specifier.
+ * @exception ParseException
+ */
+ private int namedZoneOffset(final String name) throws ParseException {
+
+ // NOTE: This is "UT", NOT "UTC"
+ if (name.equals("UT")) {
+ return 0;
+ }
+ else if (name.equals("GMT")) {
+ return 0;
+ }
+ else if (name.equals("EST")) {
+ return -300;
+ }
+ else if (name.equals("EDT")) {
+ return -240;
+ }
+ else if (name.equals("CST")) {
+ return -360;
+ }
+ else if (name.equals("CDT")) {
+ return -300;
+ }
+ else if (name.equals("MST")) {
+ return -420;
+ }
+ else if (name.equals("MDT")) {
+ return -360;
+ }
+ else if (name.equals("PST")) {
+ return -480;
+ }
+ else if (name.equals("PDT")) {
+ return -420;
+ }
+ else {
+ parseError("Invalid time zone");
+ return 0;
+ }
+ }
+
+
+ /**
+ * Parse a single-character military timezone.
+ *
+ * @param name The one-character name.
+ *
+ * @return The offset corresponding to the military designation.
+ */
+ private int militaryZoneOffset(final String name) throws ParseException {
+ switch (Character.toUpperCase(name.charAt(0))) {
+ case 'A':
+ return 60;
+ case 'B':
+ return 120;
+ case 'C':
+ return 180;
+ case 'D':
+ return 240;
+ case 'E':
+ return 300;
+ case 'F':
+ return 360;
+ case 'G':
+ return 420;
+ case 'H':
+ return 480;
+ case 'I':
+ return 540;
+ case 'K':
+ return 600;
+ case 'L':
+ return 660;
+ case 'M':
+ return 720;
+ case 'N':
+ return -60;
+ case 'O':
+ return -120;
+ case 'P':
+ return -180;
+ case 'Q':
+ return -240;
+ case 'R':
+ return -300;
+ case 'S':
+ return -360;
+ case 'T':
+ return -420;
+ case 'U':
+ return -480;
+ case 'V':
+ return -540;
+ case 'W':
+ return -600;
+ case 'X':
+ return -660;
+ case 'Y':
+ return -720;
+ case 'Z':
+ return 0;
+ default:
+ parseError("Invalid time zone");
+ return 0;
+ }
+ }
+ }
+}