geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bsny...@apache.org
Subject svn commit: r381393 [1/5] - in /geronimo/specs/trunk/geronimo-spec-javamail/src: main/java/javax/mail/internet/ main/java/org/apache/geronimo/mail/util/ main/resources/META-INF/ test/java/javax/mail/internet/
Date Mon, 27 Feb 2006 17:38:09 GMT
Author: bsnyder
Date: Mon Feb 27 09:38:03 2006
New Revision: 381393

URL: http://svn.apache.org/viewcvs?rev=381393&view=rev
Log:
Committed patches from GERONIMO-1651.

Added:
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/Base64EncoderStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintable.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableDecoderStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoderStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/RFC2231Encoder.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/SessionUtil.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/StringBufferOutputStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/UUDecoderStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/UUEncode.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoder.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoderStream.java
  (with props)
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/resources/META-INF/javamail.charset.map
Modified:
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentDisposition.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentType.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetAddress.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetHeaders.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeBodyPart.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeMessage.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeUtility.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/NewsAddress.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ParameterList.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/Base64.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/org/apache/geronimo/mail/util/XTextEncoder.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/test/java/javax/mail/internet/ContentDispositionTest.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/test/java/javax/mail/internet/ContentTypeTest.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/test/java/javax/mail/internet/MimeMessageTest.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/test/java/javax/mail/internet/NewsAddressTest.java
    geronimo/specs/trunk/geronimo-spec-javamail/src/test/java/javax/mail/internet/ParameterListTest.java

Modified: geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentDisposition.java
URL: http://svn.apache.org/viewcvs/geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentDisposition.java?rev=381393&r1=381392&r2=381393&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentDisposition.java
(original)
+++ geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentDisposition.java
Mon Feb 27 09:38:03 2006
@@ -32,14 +32,22 @@
     }
 
     public ContentDisposition(String disposition) throws ParseException {
-        ParameterList list = null;
-        int semicolon;
-        if (disposition != null && (semicolon = disposition.indexOf(";")) != -1)
{
-            list = new ParameterList(disposition.substring(semicolon + 1));
-            disposition = disposition.substring(0, semicolon);
+        // get a token parser for the type information
+        HeaderTokenizer tokenizer = new HeaderTokenizer(disposition, HeaderTokenizer.MIME);
+
+        // get the first token, which must be an ATOM
+        HeaderTokenizer.Token token = tokenizer.next();
+        if (token.getType() != HeaderTokenizer.Token.ATOM) {
+            throw new ParseException("Invalid content disposition");
+        }
+
+        _disposition = token.getValue();
+
+        // the remainder is parameters, which ParameterList will take care of parsing.
+        String remainder = tokenizer.getRemainder();
+        if (remainder != null) {
+            _list = new ParameterList(remainder);
         }
-        setDisposition(disposition);
-        setParameterList(list);
     }
 
     public ContentDisposition(String disposition, ParameterList list) {
@@ -68,7 +76,9 @@
     }
 
     public void setParameter(String name, String value) {
-        _list = new ParameterList();
+        if (_list == null) {
+            _list = new ParameterList();
+        }
         _list.set(name, value);
     }
 
@@ -81,10 +91,20 @@
     }
 
     public String toString() {
-        if (_disposition == null && _list.size() == 0) {
+        // it is possible we might have a parameter list, but this is meaningless if
+        // there is no disposition string.  Return a failure.
+        if (_disposition == null) {
             return null;
         }
-        return (_disposition == null ? "" : _disposition)
-                + (_list.size() == 0 ? "" : _list.toString());
+
+
+        // no parameter list?  Just return the disposition string
+        if (_list == null) {
+            return _disposition;
+        }
+
+        // format this for use on a Content-Disposition header, which means we need to
+        // account for the length of the header part too.
+        return _disposition + _list.toString(21 + _disposition.length());
     }
 }

Modified: geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentType.java
URL: http://svn.apache.org/viewcvs/geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentType.java?rev=381393&r1=381392&r2=381393&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentType.java
(original)
+++ geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/ContentType.java
Mon Feb 27 09:38:03 2006
@@ -28,7 +28,7 @@
     private String _major;
 
     public ContentType() {
-        this("text", "plain", new ParameterList());
+        // the Sun version makes everything null here.
     }
 
     public ContentType(String major, String minor, ParameterList list) {
@@ -38,18 +38,36 @@
     }
 
     public ContentType(String type) throws ParseException {
-        final int slash = type.indexOf("/");
-        final int semi = type.indexOf(";");
-        try {
-            _major = type.substring(0, slash);
-            if (semi == -1) {
-                _minor = type.substring(slash + 1);
-            } else {
-                _minor = type.substring(slash + 1, semi);
-                _list = new ParameterList(type.substring(semi + 1));
-            }
-        } catch (StringIndexOutOfBoundsException e) {
-            throw new ParseException("Type invalid: " + type);
+        // get a token parser for the type information
+        HeaderTokenizer tokenizer = new HeaderTokenizer(type, HeaderTokenizer.MIME);
+
+        // get the first token, which must be an ATOM
+        HeaderTokenizer.Token token = tokenizer.next();
+        if (token.getType() != HeaderTokenizer.Token.ATOM) {
+            throw new ParseException("Invalid content type");
+        }
+
+        _major = token.getValue();
+
+        // the MIME type must be major/minor
+        token = tokenizer.next();
+        if (token.getType() != '/') {
+            throw new ParseException("Invalid content type");
+        }
+
+
+        // this must also be an atom.  Content types are not permitted to be wild cards.
+        token = tokenizer.next();
+        if (token.getType() != HeaderTokenizer.Token.ATOM) {
+            throw new ParseException("Invalid content type");
+        }
+
+        _minor = token.getValue();
+
+        // the remainder is parameters, which ParameterList will take care of parsing.
+        String remainder = tokenizer.getRemainder();
+        if (remainder != null) {
+            _list = new ParameterList(remainder);
         }
     }
 
@@ -93,12 +111,16 @@
     }
 
     public String toString() {
-        return getBaseType() + (_list == null ? "" : ";" + _list.toString());
+        if (_major == null || _minor == null) {
+            return null;
+        }
+
+        return getBaseType() + (_list == null ? "" : _list.toString());
     }
 
     public boolean match(ContentType other) {
-        return _major.equals(other._major)
-                && (_minor.equals(other._minor)
+        return _major.equalsIgnoreCase(other._major)
+                && (_minor.equalsIgnoreCase(other._minor)
                 || _minor.equals("*")
                 || other._minor.equals("*"));
     }

Modified: geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java
URL: http://svn.apache.org/viewcvs/geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java?rev=381393&r1=381392&r2=381393&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java
(original)
+++ geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java
Mon Feb 27 09:38:03 2006
@@ -89,58 +89,189 @@
     }
 
     /**
-     * @return
+     * 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
         int start = pos;
-        while (++pos < _header.length()
-                && _delimiters.indexOf(_header.charAt(pos)) == -1)
-            ;
+        while (++pos < _header.length()) {
+            // break on the first non-atom character.
+            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() throws ParseException {
         if (pos >= _header.length()) {
             return EOF;
         } else {
             char c = _header.charAt(pos);
+            // comment token...read and skip over this
             if (c == '(') {
-                Token comment = readUntil(')', Token.COMMENT);
+                Token comment = readComment();
                 if (_skip) {
                     return readToken();
                 } else {
                     return comment;
                 }
+                // quoted literal
             } else if (c == '\"') {
-                return readUntil('\"', Token.QUOTEDSTRING);
+                return readQuotedString();
+            // white space, eat this and find a real token.
             } else if (WHITE.indexOf(c) != -1) {
                 eatWhiteSpace();
                 return readToken();
-            } else if (_delimiters.indexOf(c) != -1) {
+            // either a CTL or special.  These characters have a self-defining token type.
+            } else if (c < 32 || c >= 127 || _delimiters.indexOf(c) != -1) {
                 pos++;
-                return new Token(Token.ATOM, String.valueOf(c));
+                return new Token((int)c, String.valueOf(c));
             } else {
+                // start of an atom, parse it off.
                 return readAtomicToken();
             }
         }
     }
 
     /**
-     * @return
+     * 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(int start, int end) throws ParseException {
+        StringBuffer value = new StringBuffer();
+
+        for (int i = start; i < end; i++) {
+            char ch = _header.charAt(i);
+            // is this an escape character?
+            if (ch == '\\') {
+                i++;
+                if (i == end) {
+                    throw new ParseException("Invalid escape character");
+                }
+                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() throws ParseException {
+        int start = pos + 1;
+        int nesting = 1;
+
+        boolean requiresEscaping = false;
+
+        // skip to end of comment/string
+        while (++pos < _header.length()) {
+            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);
+        }
+        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 readUntil(char end, int type) {
-        int start = ++pos;
+    private Token readQuotedString() throws ParseException {
+        int start = pos+1;
+        boolean requiresEscaping = false;
+
         // skip to end of comment/string
-        while (++pos < _header.length()
-                && _header.charAt(pos) != end)
-            ;
-        String value = _header.substring(start, pos++);
-        return new Token(type, value);
+        while (++pos < _header.length()) {
+            char ch = _header.charAt(pos);
+            if (ch == '"') {
+                String value;
+                if (requiresEscaping) {
+                    value = getEscapedValue(start, pos);
+                }
+                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 '\"'");
     }
 
     /**
-     * @return
+     * Skip white space in the token string.
      */
     private void eatWhiteSpace() {
         // skip to end of whitespace

Modified: geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetAddress.java
URL: http://svn.apache.org/viewcvs/geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetAddress.java?rev=381393&r1=381392&r2=381393&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetAddress.java
(original)
+++ geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetAddress.java
Mon Feb 27 09:38:03 2006
@@ -129,8 +129,13 @@
      * @throws UnsupportedEncodingException if the name cannot be encoded
      */
     public void setPersonal(String name, String charset) throws UnsupportedEncodingException
{
-        encodedPersonal = MimeUtility.encodeWord(name, charset, null);
         personal = name;
+        if (name != null) {
+            encodedPersonal = MimeUtility.encodeWord(name, charset, null);
+        }
+        else {
+            encodedPersonal = null;
+        }
     }
 
     /**
@@ -142,8 +147,13 @@
      * @throws UnsupportedEncodingException if the name cannot be encoded
      */
     public void setPersonal(String name) throws UnsupportedEncodingException {
-        encodedPersonal = MimeUtility.encodeWord(name);
         personal = name;
+        if (name != null) {
+            encodedPersonal = MimeUtility.encodeWord(name);
+        }
+        else {
+            encodedPersonal = null;
+        }
     }
 
     /**

Modified: geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetHeaders.java
URL: http://svn.apache.org/viewcvs/geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetHeaders.java?rev=381393&r1=381392&r2=381393&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetHeaders.java
(original)
+++ geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/InternetHeaders.java
Mon Feb 27 09:38:03 2006
@@ -42,7 +42,7 @@
 public class InternetHeaders {
     // RFC822 imposes an ordering on its headers so we use a LinkedHashedMap
     private final LinkedHashMap headers = new LinkedHashMap();
-    
+
     private transient String lastHeaderName;
 
     /**
@@ -198,7 +198,7 @@
             return null;
         } else if (list.isEmpty()) {
             return "";
-        } else if (list.size() == 1) {
+        } else if (list.size() == 1 || delimiter == null) {
             return ((InternetHeader) list.get(0)).getValue();
         } else {
             StringBuffer buf = new StringBuffer(20 * list.size());
@@ -305,11 +305,11 @@
     }
 
     /**
-     * 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. 
+     * 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
      */
@@ -321,7 +321,7 @@
         if (Character.isWhitespace(line.charAt(0))) {
         	continuation = true;
         	inName = false;
-        } 
+        }
         for (int i = 0; i < line.length(); i++) {
         	char c = line.charAt(i);
         	if (inName && c == ':') {
@@ -344,7 +344,7 @@
 
     /**
      * Return all the header lines as an Enumeration of Strings.
-     */         
+     */
     public Enumeration getAllHeaderLines() {
         return new HeaderLineEnumeration(getAllHeaders());
     }
@@ -371,20 +371,34 @@
      * @param strict whether the header should be strictly parser; see {@link InternetAddress#parseHeader(java.util.List,
String, boolean, boolean)}
      * @return
      */
-    InternetAddress[] getHeaderAsAddresses(String name, boolean strict) throws MessagingException
{
+    Address[] getHeaderAsAddresses(String name, boolean strict) throws MessagingException
{
         List addrs = new ArrayList();
         List headers = getHeaderList(name);
         if (headers == null) {
             return null;
         }
+        // news groups are a special case.  Those are NewsAddress items, not InternetAddress.
+        boolean isNewsGroup = name.equals("Newsgroups");
+
         for (Iterator i = headers.iterator(); i.hasNext();) {
             InternetHeader header = (InternetHeader) i.next();
-            InternetAddress[] addresses = InternetAddress.parseHeader(header.getValue(),
strict);
-            for (int j = 0; j < addresses.length; j++) {
-                addrs.add(addresses[j]);
+            Address[] addresses = null;
+            // removed headers may show up as value-less entities, so we need to make
+            // sure this is real before attempting to parse the value.
+            String headerValue = header.getValue();
+            if (headerValue != null) {
+                if (isNewsGroup) {
+                    addresses = NewsAddress.parse(header.getValue());
+                }
+                else {
+                    addresses = InternetAddress.parseHeader(header.getValue(), strict);
+                }
+                for (int j = 0; j < addresses.length; j++) {
+                    addrs.add(addresses[j]);
+                }
             }
         }
-        return (InternetAddress[]) addrs.toArray(new InternetAddress[addrs.size()]);
+        return (Address[]) addrs.toArray(new Address[addrs.size()]);
     }
 
     void setHeader(String name, Address[] addresses) {
@@ -442,10 +456,10 @@
             return getName().toLowerCase().hashCode();
         }
     }
-    
+
     private static class HeaderLineEnumeration implements Enumeration {
     	private Enumeration headers;
-    	
+
 		public HeaderLineEnumeration(Enumeration headers) {
 			this.headers = headers;
 		}

Modified: geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeBodyPart.java
URL: http://svn.apache.org/viewcvs/geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeBodyPart.java?rev=381393&r1=381392&r2=381393&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeBodyPart.java
(original)
+++ geronimo/specs/trunk/geronimo-spec-javamail/src/main/java/javax/mail/internet/MimeBodyPart.java
Mon Feb 27 09:38:03 2006
@@ -22,16 +22,27 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
 import java.util.Enumeration;
 import javax.activation.DataHandler;
 import javax.mail.BodyPart;
 import javax.mail.MessagingException;
 import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.internet.HeaderTokenizer.Token;
+
+import org.apache.geronimo.mail.util.SessionUtil;
+import org.apache.geronimo.mail.util.ASCIIUtil;
 
 /**
  * @version $Rev$ $Date$
  */
 public class MimeBodyPart extends BodyPart implements MimePart {
+	 // constants for accessed properties
+    protected static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
+    protected static final String MIME_SETDEFAULTTEXTCHARSET = "mail.mime.setdefaulttextcharset";
+
+
     /**
      * The {@link DataHandler} for this Message's content.
      */
@@ -74,10 +85,28 @@
         this.content = content;
     }
 
+    /**
+     * Return the content size of this message.  This is obtained
+     * either from the size of the content field (if available) or
+     * from the contentStream, IFF the contentStream returns a positive
+     * size.  Returns -1 if the size is not available.
+     *
+     * @return Size of the content in bytes.
+     * @exception MessagingException
+     */
     public int getSize() throws MessagingException {
         if (content != null) {
             return content.length;
         }
+        if (contentStream != null) {
+            try {
+                int size = contentStream.available();
+                if (size > 0) {
+                    return size;
+                }
+            } catch (IOException e) {
+            }
+        }
         return -1;
     }
 
@@ -93,28 +122,107 @@
         return value;
     }
 
+    /**
+     * Tests to see if this message has a mime-type match with the
+     * given type name.
+     *
+     * @param type   The tested type name.
+     *
+     * @return If this is a type match on the primary and secondare portion of the types.
+     * @exception MessagingException
+     */
     public boolean isMimeType(String type) throws MessagingException {
         return new ContentType(getContentType()).match(type);
     }
 
+    /**
+     * Retrieve the message "Content-Disposition" header field.
+     * This value represents how the part should be represented to
+     * the user.
+     *
+     * @return The string value of the Content-Disposition field.
+     * @exception MessagingException
+     */
     public String getDisposition() throws MessagingException {
-        return getSingleHeader("Content-Disposition");
+        String disp = getSingleHeader("Content-Disposition");
+        if (disp != null) {
+            return new ContentDisposition(disp).getDisposition();
+        }
+        return null;
     }
 
+    /**
+     * Set a new dispostion value for the "Content-Disposition" field.
+     * If the new value is null, the header is removed.
+     *
+     * @param disposition
+     *               The new disposition value.
+     *
+     * @exception MessagingException
+     */
     public void setDisposition(String disposition) throws MessagingException {
-        setHeader("Content-Disposition", disposition);
+        if (disposition == null) {
+            removeHeader("Content-Disposition");
+        }
+        else {
+            // the disposition has parameters, which we'll attempt to preserve in any existing
header.
+            String currentHeader = getSingleHeader("Content-Disposition");
+            if (currentHeader != null) {
+                ContentDisposition content = new ContentDisposition(currentHeader);
+                content.setDisposition(disposition);
+                setHeader("Content-Disposition", content.toString());
+            }
+            else {
+                // set using the raw string.
+                setHeader("Content-Disposition", disposition);
+            }
+        }
     }
 
+    /**
+     * Retrieves the current value of the "Content-Transfer-Encoding"
+     * header.  Returns null if the header does not exist.
+     *
+     * @return The current header value or null.
+     * @exception MessagingException
+     */
     public String getEncoding() throws MessagingException {
-        return getSingleHeader("Content-Transfer-Encoding");
+        // this might require some parsing to sort out.
+        String encoding = getSingleHeader("Content-Transfer-Encoding");
+        if (encoding != null) {
+            // we need to parse this into ATOMs and other constituent parts.  We want the
first
+            // ATOM token on the string.
+            HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
+
+            Token token = tokenizer.next();
+            while (token.getType() != Token.EOF) {
+                // if this is an ATOM type, return it.
+                if (token.getType() == Token.ATOM) {
+                    return token.getValue();
+                }
+            }
+            // not ATOMs found, just return the entire header value....somebody might be
able to make sense of
+            // this.
+            return encoding;
+        }
+        // no header, nothing to return.
+        return null;
     }
 
+
+    /**
+     * Retrieve the value of the "Content-ID" header.  Returns null
+     * if the header does not exist.
+     *
+     * @return The current header value or null.
+     * @exception MessagingException
+     */
     public String getContentID() throws MessagingException {
         return getSingleHeader("Content-ID");
     }
 
     public void setContentID(String cid) throws MessagingException {
-        setHeader("Content-ID", cid);
+        setOrRemoveHeader("Content-ID", cid);
     }
 
     public String getContentMD5() throws MessagingException {
@@ -130,7 +238,7 @@
     }
 
     public void setContentLanguage(String[] languages) throws MessagingException {
-        if (languages == null || languages.length == 0) {
+        if (languages == null) {
             removeHeader("Content-Language");
         } else if (languages.length == 1) {
             setHeader("Content-Language", languages[0]);
@@ -149,22 +257,78 @@
     }
 
     public void setDescription(String description) throws MessagingException {
-        setHeader("Content-Description", description);
+        setDescription(description, null);
     }
 
     public void setDescription(String description, String charset) throws MessagingException
{
-        // todo encoding
-        setHeader("Content-Description", description);
+        if (description == null) {
+            removeHeader("Content-Description");
+        }
+        else {
+            try {
+                setHeader("Content-Description", ASCIIUtil.fold(21, MimeUtility.encodeText(description,
charset, null)));
+            } catch (UnsupportedEncodingException e) {
+                throw new MessagingException(e.getMessage(), e);
+            }
+        }
     }
 
     public String getFileName() throws MessagingException {
-        // TODO Implement method
-        throw new UnsupportedOperationException("Method not yet implemented");
+        // see if there is a disposition.  If there is, parse off the filename parameter.
+        String disposition = getDisposition();
+        String filename = null;
+
+        if (disposition != null) {
+            filename = new ContentDisposition(disposition).getParameter("filename");
+        }
+
+        // if there's no filename on the disposition, there might be a name parameter on
a
+        // Content-Type header.
+        if (filename == null) {
+            String type = getContentType();
+            if (type != null) {
+                try {
+                    filename = new ContentType(type).getParameter("name");
+                } catch (ParseException e) {
+                }
+            }
+        }
+        // if we have a name, we might need to decode this if an additional property is set.
+        if (filename != null && SessionUtil.getBooleanProperty(MIME_DECODEFILENAME,
false)) {
+            try {
+                filename = MimeUtility.decodeText(filename);
+            } catch (UnsupportedEncodingException e) {
+                throw new MessagingException("Unable to decode filename", e);
+            }
+        }
+
+        return filename;
     }
 
+
     public void setFileName(String name) throws MessagingException {
-        // TODO Implement method
-        throw new UnsupportedOperationException("Method not yet implemented");
+        // there's an optional session property that requests file name encoding...we need
to process this before
+        // setting the value.
+        if (name == null) {
+            try {
+                name = MimeUtility.encodeText(name);
+            } catch (UnsupportedEncodingException e) {
+                throw new MessagingException("Unable to encode filename", e);
+            }
+        }
+
+        // get the disposition string.
+        String disposition = getDisposition();
+        // if not there, then this is an attachment.
+        if (disposition == null) {
+            disposition = Part.ATTACHMENT;
+        }
+        // now create a disposition object and set the parameter.
+        ContentDisposition contentDisposition = new ContentDisposition(disposition);
+        contentDisposition.setParameter("filename", name);
+
+        // serialize this back out and reset.
+        setDisposition(contentDisposition.toString());
     }
 
     public InputStream getInputStream() throws MessagingException, IOException {
@@ -172,6 +336,10 @@
     }
 
     protected InputStream getContentStream() throws MessagingException {
+        if (contentStream != null) {
+            return contentStream;
+        }
+
         if (content != null) {
             return new ByteArrayInputStream(content);
         } else {
@@ -203,11 +371,21 @@
     }
 
     public void setText(String text) throws MessagingException {
-        setText(text, MimeUtility.getDefaultJavaCharset());
+        setText(text, null);
     }
 
     public void setText(String text, String charset) throws MessagingException {
-        setContent(text, "text/plain; charset=" + charset);
+        // we need to sort out the character set if one is not provided.
+        if (charset == null) {
+            // if we have non us-ascii characters here, we need to adjust this.
+            if (!ASCIIUtil.isAscii(text)) {
+                charset = MimeUtility.getDefaultMIMECharset();
+            }
+            else {
+                charset = "us-ascii";
+            }
+        }
+        setContent(text, "text/plain; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
     }
 
     public void setContent(Multipart part) throws MessagingException {
@@ -217,9 +395,11 @@
 
     public void writeTo(OutputStream out) throws IOException, MessagingException {
         headers.writeTo(out, null);
-        out.write(13);
-        out.write(10);
-        getDataHandler().writeTo(out);
+        // add the separater between the headers and the data portion.
+        out.write('\r');
+        out.write('\n');
+        // we need to process this using the transfer encoding type
+        getDataHandler().writeTo(MimeUtility.encode(out, getEncoding()));
     }
 
     public String[] getHeader(String name) throws MessagingException {
@@ -234,6 +414,25 @@
         headers.setHeader(name, value);
     }
 
+    /**
+     * Conditionally set or remove a named header.  If the new value
+     * is null, the header is removed.
+     *
+     * @param name   The header name.
+     * @param value  The new header value.  A null value causes the header to be
+     *               removed.
+     *
+     * @exception MessagingException
+     */
+    private void setOrRemoveHeader(String name, String value) throws MessagingException {
+        if (value == null) {
+            headers.removeHeader(name);
+        }
+        else {
+            headers.setHeader(name, value);
+        }
+    }
+
     public void addHeader(String name, String value) throws MessagingException {
         headers.addHeader(name, value);
     }
@@ -271,6 +470,77 @@
     }
 
     protected void updateHeaders() throws MessagingException {
+        DataHandler handler = getDataHandler();
+
+        try {
+            // figure out the content type.  If not set, we'll need to figure this out.
+            String type = getContentType();
+            // parse this content type out so we can do matches/compares.
+            ContentType content = new ContentType(type);
+            // is this a multipart content?
+            if (content.match("multipart/*")) {
+                // the content is suppose to be a MimeMultipart.  Ping it to update it's
headers as well.
+                try {
+                    MimeMultipart part = (MimeMultipart)handler.getContent();
+                    part.updateHeaders();
+                } catch (ClassCastException e) {
+                    throw new MessagingException("Message content is not MimeMultipart",
e);
+                }
+            }
+            else if (!content.match("message/rfc822")) {
+                // simple part, we need to update the header type information
+                // if no encoding is set yet, figure this out from the data handler.
+                if (getHeader("Content-Transfer-Encoding") == null) {
+                    setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
+                }
+
+                // is a content type header set?  Check the property to see if we need to
set this.
+                if (getHeader("Content-Type") == null) {
+                    if (SessionUtil.getBooleanProperty(MIME_SETDEFAULTTEXTCHARSET, true))
{
+                        // is this a text type?  Figure out the encoding and make sure it
is set.
+                        if (content.match("text/*")) {
+                            // the charset should be specified as a parameter on the MIME
type.  If not there,
+                            // try to figure one out.
+                            if (content.getParameter("charset") == null) {
+
+                                String encoding = getEncoding();
+                                // if we're sending this as 7-bit ASCII, our character set
need to be
+                                // compatible.
+                                if (encoding != null && encoding.equalsIgnoreCase("7bit"))
{
+                                    content.setParameter("charset", "us-ascii");
+                                }
+                                else {
+                                    // get the global default.
+                                    content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            // if we don't have a content type header, then create one.
+            if (getHeader("Content-Type") == null) {
+                // get the disposition header, and if it is there, copy the filename parameter
into the
+                // name parameter of the type.
+                String disp = getHeader("Content-Disposition", null);
+                if (disp != null) {
+                    // parse up the string value of the disposition
+                    ContentDisposition disposition = new ContentDisposition(disp);
+                    // now check for a filename value
+                    String filename = disposition.getParameter("filename");
+                    // copy and rename the parameter, if it exists.
+                    if (filename != null) {
+                        content.setParameter("name", filename);
+                    }
+                }
+                // set the header with the updated content type information.
+                setHeader("Content-Type", content.toString());
+            }
+
+        } catch (IOException e) {
+            throw new MessagingException("Error updating message headers", e);
+        }
     }
 
     private String getSingleHeader(String name) throws MessagingException {



Mime
View raw message