You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mime4j-dev@james.apache.org by mw...@apache.org on 2009/02/27 17:45:03 UTC

svn commit: r748583 [1/2] - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/field/ main/java/org/apache/james/mime4j/message/ main/java/org/apache/james/mime4j/parser/ main/java/org/apache/james/mime4j/util/ test/java/org/apache/james/mim...

Author: mwiederkehr
Date: Fri Feb 27 16:45:02 2009
New Revision: 748583

URL: http://svn.apache.org/viewvc?rev=748583&view=rev
Log:
Fix for MIME4J-118: MIME stream parser handles non-ASCII fields incorrectly

Added:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java
Modified:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/field/FieldsTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/EntityTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/ExampleMessagesRoundtripTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MessageParserTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MessageTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MessageWriteToTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MultipartFormTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/parser/MimeStreamParserTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/parser/TestHandler.java

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java Fri Feb 27 16:45:02 2009
@@ -24,6 +24,8 @@
 
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.ContentUtil;
 import org.apache.james.mime4j.util.MimeUtil;
 
 /**
@@ -31,23 +33,37 @@
  */
 public abstract class AbstractField implements Field {
 
-    private static final String FIELD_NAME_PATTERN = 
-        "^([\\x21-\\x39\\x3b-\\x7e]+):";
-    private static final Pattern fieldNamePattern = 
-        Pattern.compile(FIELD_NAME_PATTERN);
-        
+    private static final Pattern FIELD_NAME_PATTERN = Pattern
+            .compile("^([\\x21-\\x39\\x3b-\\x7e]+):");
+
     private static final DefaultFieldParser parser = new DefaultFieldParser();
     
     private final String name;
     private final String body;
-    private final String raw;
+    private final ByteSequence raw;
     
-    protected AbstractField(final String name, final String body, final String raw) {
+    protected AbstractField(final String name, final String body, final ByteSequence raw) {
         this.name = name;
         this.body = body;
         this.raw = raw;
     }
-    
+
+    /**
+     * Parses the given byte sequence and returns an instance of the
+     * <code>Field</code> class. The type of the class returned depends on the
+     * field name; see {@link #parse(String)} for a table of field names and
+     * their corresponding classes.
+     * 
+     * @param raw the bytes to parse.
+     * @return a <code>Field</code> instance.
+     * @throws MimeException if the raw string cannot be split into field name and body.
+     * @see #isValidField()
+     */
+    public static Field parse(final ByteSequence raw) throws MimeException {
+        String rawStr = ContentUtil.decode(raw);
+        return parse(raw, rawStr);
+    }
+
     /**
      * Parses the given string and returns an instance of the 
      * <code>Field</code> class. The type of the class returned depends on
@@ -65,69 +81,19 @@
      *   <tr><td>{@link UnstructuredField}</td><td>Subject and others</td></tr>
      * </table>
      * 
-     * @param raw the string to parse.
+     * @param rawStr the string to parse.
      * @return a <code>Field</code> instance.
      * @throws MimeException if the raw string cannot be split into field name and body.
      * @see #isValidField()
      */
-    public static Field parse(final String raw) throws MimeException {
-        
-        /*
-         * Unfold the field.
-         */
-        final String unfolded = MimeUtil.unfold(raw);
-        
-        /*
-         * Split into name and value.
-         */
-        final Matcher fieldMatcher = fieldNamePattern.matcher(unfolded);
-        if (!fieldMatcher.find()) {
-            throw new MimeException("Invalid field in string");
-        }
-        final String name = fieldMatcher.group(1);
-        
-        String body = unfolded.substring(fieldMatcher.end());
-        if (body.length() > 0 && body.charAt(0) == ' ') {
-            body = body.substring(1);
-        }
-        
-        return parser.parse(name, body, raw);
-    }
-
-    /**
-     * Parses the given field name and field body strings and returns an
-     * instance of the <code>Field</code> class. The type of the class
-     * returned depends on the field name (see {@link #parse(String)}).
-     * <p>
-     * This method is convenient for creating or manipulating messages because
-     * contrary to {@link #parse(String)} it does not throw a
-     * {@link MimeException}.
-     * <p>
-     * Note that this method does not fold the header field; the specified field
-     * body should already have been folded into multiple lines prior to calling
-     * this method if folding is desired.
-     * 
-     * @param name
-     *            the field name.
-     * @param body
-     *            the field body (a.k.a value).
-     * @return a <code>Field</code> instance.
-     */
-    public static Field parse(String name, String body) {
-        if (body.length() > 0 && body.charAt(0) == ' ') {
-            body = body.substring(1);
-        }
-
-        String raw = name + ": " + body;
-
-        // Unfold body
-        body = MimeUtil.unfold(body);
-
-        return parser.parse(name, body, raw);
+    public static Field parse(final String rawStr) throws MimeException {
+        ByteSequence raw = ContentUtil.encode(rawStr);
+        return parse(raw, rawStr);
     }
 
     /**
      * Gets the default parser used to parse fields.
+     * 
      * @return the default field parser
      */
     public static DefaultFieldParser getParser() {
@@ -149,7 +115,7 @@
      * 
      * @return the original raw field string.
      */
-    public String getRaw() {
+    public ByteSequence getRaw() {
         return raw;
     }
     
@@ -227,11 +193,33 @@
         return FieldName.TO.equalsIgnoreCase(name);
     }
     
-    /**
-     * @see #getRaw()
-     */
     @Override
     public String toString() {
-        return raw;
+        return name + ": " + body;
+    }
+
+    private static Field parse(final ByteSequence raw, final String rawStr)
+            throws MimeException {
+        /*
+         * Unfold the field.
+         */
+        final String unfolded = MimeUtil.unfold(rawStr);
+
+        /*
+         * Split into name and value.
+         */
+        final Matcher fieldMatcher = FIELD_NAME_PATTERN.matcher(unfolded);
+        if (!fieldMatcher.find()) {
+            throw new MimeException("Invalid field in string");
+        }
+        final String name = fieldMatcher.group(1);
+
+        String body = unfolded.substring(fieldMatcher.end());
+        if (body.length() > 0 && body.charAt(0) == ' ') {
+            body = body.substring(1);
+        }
+
+        return parser.parse(name, body, raw);
     }
+
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java Fri Feb 27 16:45:02 2009
@@ -24,6 +24,7 @@
 import org.apache.james.mime4j.field.address.AddressList;
 import org.apache.james.mime4j.field.address.parser.ParseException;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Address list field such as <code>To</code> or <code>Reply-To</code>.
@@ -36,7 +37,7 @@
     private AddressList addressList;
     private ParseException parseException;
 
-    AddressListField(String name, String body, String raw) {
+    AddressListField(String name, String body, ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -72,7 +73,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new AddressListField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java Fri Feb 27 16:45:02 2009
@@ -33,6 +33,7 @@
 import org.apache.james.mime4j.field.contentdisposition.parser.TokenMgrError;
 import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Represents a <code>Content-Disposition</code> field.
@@ -76,7 +77,7 @@
     private boolean readDateParsed;
     private Date readDate;
 
-    ContentDispositionField(String name, String body, String raw) {
+    ContentDispositionField(String name, String body, ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -319,7 +320,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new ContentDispositionField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java Fri Feb 27 16:45:02 2009
@@ -20,6 +20,7 @@
 package org.apache.james.mime4j.field;
 
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.MimeUtil;
 
 /**
@@ -28,7 +29,7 @@
 public class ContentTransferEncodingField extends AbstractField {
     private String encoding;
 
-    ContentTransferEncodingField(String name, String body, String raw) {
+    ContentTransferEncodingField(String name, String body, ByteSequence raw) {
         super(name, body, raw);
         encoding = body.trim().toLowerCase();
     }
@@ -58,7 +59,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new ContentTransferEncodingField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java Fri Feb 27 16:45:02 2009
@@ -31,6 +31,7 @@
 import org.apache.james.mime4j.field.contenttype.parser.ParseException;
 import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Represents a <code>Content-Type</code> field.
@@ -62,7 +63,7 @@
     private Map<String, String> parameters = new HashMap<String, String>();
     private ParseException parseException;
 
-    ContentTypeField(String name, String body, String raw) {
+    ContentTypeField(String name, String body, ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -251,7 +252,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new ContentTypeField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java Fri Feb 27 16:45:02 2009
@@ -28,6 +28,7 @@
 import org.apache.james.mime4j.field.datetime.parser.ParseException;
 import org.apache.james.mime4j.field.datetime.parser.TokenMgrError;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Date-time field such as <code>Date</code> or <code>Resent-Date</code>.
@@ -40,7 +41,7 @@
     private Date date;
     private ParseException parseException;
 
-    DateTimeField(String name, String body, String raw) {
+    DateTimeField(String name, String body, ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -82,7 +83,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new DateTimeField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java Fri Feb 27 16:45:02 2009
@@ -23,6 +23,7 @@
 import java.util.Map;
 
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 public class DelegatingFieldParser implements FieldParser {
     
@@ -46,7 +47,7 @@
         return field;
     }
     
-    public Field parse(final String name, final String body, final String raw) {
+    public Field parse(final String name, final String body, final ByteSequence raw) {
         final FieldParser parser = getParser(name);
         return parser.parse(name, body, raw);
     }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java Fri Feb 27 16:45:02 2009
@@ -20,9 +20,10 @@
 package org.apache.james.mime4j.field;
 
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 public interface FieldParser {
     
-    Field parse(final String name, final String body, final String raw);
+    Field parse(final String name, final String body, final ByteSequence raw);
     
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java Fri Feb 27 16:45:02 2009
@@ -31,6 +31,8 @@
 import org.apache.james.mime4j.field.address.Address;
 import org.apache.james.mime4j.field.address.Mailbox;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.ContentUtil;
 import org.apache.james.mime4j.util.MimeUtil;
 
 /**
@@ -602,7 +604,8 @@
 
     private static <F extends Field> F parse(FieldParser parser,
             String fieldName, String fieldBody) {
-        String raw = MimeUtil.fold(fieldName + ": " + fieldBody, 0);
+        String rawStr = MimeUtil.fold(fieldName + ": " + fieldBody, 0);
+        ByteSequence raw = ContentUtil.encode(rawStr);
 
         Field field = parser.parse(fieldName, fieldBody, raw);
 

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java Fri Feb 27 16:45:02 2009
@@ -26,6 +26,7 @@
 import org.apache.james.mime4j.field.address.MailboxList;
 import org.apache.james.mime4j.field.address.parser.ParseException;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Mailbox field such as <code>Sender</code> or <code>Resent-Sender</code>.
@@ -38,7 +39,7 @@
     private Mailbox mailbox;
     private ParseException parseException;
 
-    MailboxField(final String name, final String body, final String raw) {
+    MailboxField(final String name, final String body, final ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -77,7 +78,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new MailboxField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java Fri Feb 27 16:45:02 2009
@@ -25,6 +25,7 @@
 import org.apache.james.mime4j.field.address.MailboxList;
 import org.apache.james.mime4j.field.address.parser.ParseException;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Mailbox-list field such as <code>From</code> or <code>Resent-From</code>.
@@ -37,7 +38,7 @@
     private MailboxList mailboxList;
     private ParseException parseException;
 
-    MailboxListField(final String name, final String body, final String raw) {
+    MailboxListField(final String name, final String body, final ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -73,7 +74,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new MailboxListField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java Fri Feb 27 16:45:02 2009
@@ -21,6 +21,7 @@
 
 import org.apache.james.mime4j.codec.DecoderUtil;
 import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
 
 /**
  * Simple unstructured field such as <code>Subject</code>.
@@ -30,7 +31,7 @@
 
     private String value;
 
-    UnstructuredField(String name, String body, String raw) {
+    UnstructuredField(String name, String body, ByteSequence raw) {
         super(name, body, raw);
     }
 
@@ -51,7 +52,7 @@
 
     static final FieldParser PARSER = new FieldParser() {
         public Field parse(final String name, final String body,
-                final String raw) {
+                final ByteSequence raw) {
             return new UnstructuredField(name, body, raw);
         }
     };

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java Fri Feb 27 16:45:02 2009
@@ -146,18 +146,16 @@
 
     /**
      * Write the content to the given output stream using the
-     * {@link MessageWriter#STRICT_ERROR STRICT_ERROR} message writer.
+     * {@link MessageWriter#DEFAULT default} message writer.
      * 
      * @param out
      *            the output stream to write to.
      * @throws IOException
      *             in case of an I/O error
-     * @throws MimeIOException
-     *             in case of a MIME protocol violation
      * @see MessageWriter
      */
-    public void writeTo(OutputStream out) throws IOException, MimeIOException {
-        MessageWriter.STRICT_ERROR.writeEntity(this, out);
+    public void writeTo(OutputStream out) throws IOException {
+        MessageWriter.DEFAULT.writeEntity(this, out);
     }
 
     /**

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java Fri Feb 27 16:45:02 2009
@@ -32,6 +32,8 @@
 import org.apache.james.mime4j.parser.Field;
 import org.apache.james.mime4j.parser.MimeStreamParser;
 import org.apache.james.mime4j.storage.StorageProvider;
+import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.MimeUtil;
 
 /**
@@ -183,12 +185,8 @@
      */
     public void epilogue(InputStream is) throws MimeException, IOException {
         expect(Multipart.class);
-        StringBuilder sb = new StringBuilder(128);
-        int b;
-        while ((b = is.read()) != -1) {
-            sb.append((char) b);
-        }
-        ((Multipart) stack.peek()).setEpilogue(sb.toString());
+        ByteSequence bytes = loadStream(is);
+        ((Multipart) stack.peek()).setEpilogueRaw(bytes);
     }
     
     /**
@@ -196,12 +194,8 @@
      */
     public void preamble(InputStream is) throws MimeException, IOException {
         expect(Multipart.class);
-        StringBuilder sb = new StringBuilder(128);
-        int b;
-        while ((b = is.read()) != -1) {
-            sb.append((char) b);
-        }
-        ((Multipart) stack.peek()).setPreamble(sb.toString());
+        ByteSequence bytes = loadStream(is);
+        ((Multipart) stack.peek()).setPreambleRaw(bytes);
     }
     
     /**
@@ -212,4 +206,15 @@
         throw new UnsupportedOperationException("Not supported");
     }
 
+    private static ByteSequence loadStream(InputStream in) throws IOException {
+        ByteArrayBuffer bab = new ByteArrayBuffer(64);
+
+        int b;
+        while ((b = in.read()) != -1) {
+            bab.append(b);
+        }
+
+        return bab;
+    }
+
 }
\ No newline at end of file

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java Fri Feb 27 16:45:02 2009
@@ -19,77 +19,36 @@
 
 package org.apache.james.mime4j.message;
 
-import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CodingErrorAction;
 
-import org.apache.james.mime4j.MimeIOException;
 import org.apache.james.mime4j.codec.CodecUtil;
 import org.apache.james.mime4j.field.ContentTypeField;
 import org.apache.james.mime4j.field.FieldName;
 import org.apache.james.mime4j.parser.Field;
-import org.apache.james.mime4j.util.CharsetUtil;
+import org.apache.james.mime4j.util.ByteArrayBuffer;
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.ContentUtil;
 import org.apache.james.mime4j.util.MimeUtil;
 
 /**
  * Writes a message (or a part of a message) to an output stream.
  * <p>
- * This class cannot be instantiated; instead the three static instances
- * {@link #STRICT_ERROR}, {@link #STRICT_IGNORE} or {@link #LENIENT} implement
- * different strategies for encoding header fields and preamble and epilogue of
- * a multipart.
+ * This class cannot be instantiated; instead the static instance
+ * {@link #DEFAULT} implements the default strategy for writing a message.
  * <p>
- * This class can also be subclassed to implement custom strategies for writing
+ * This class may be subclassed to implement custom strategies for writing
  * messages.
  */
 public class MessageWriter {
 
-    private static final Charset LENIENT_FALLBACK_CHARSET = CharsetUtil.ISO_8859_1;
-    private static final String CRLF = CharsetUtil.CRLF;
-    private static final int WRITER_BUFFER_SIZE = 4096;
+    private static final byte[] CRLF = { '\r', '\n' };
+    private static final byte[] DASHES = { '-', '-' };
 
     /**
-     * A message writer that uses US-ASCII for encoding and throws
-     * {@link MimeIOException} if a non ASCII character is encountered.
+     * The default message writer.
      */
-    public static final MessageWriter STRICT_ERROR = new MessageWriter();
-
-    /**
-     * A message writer that uses US-ASCII for encoding but ignores non ASCII
-     * characters.
-     */
-    public static final MessageWriter STRICT_IGNORE = new MessageWriter() {
-        @Override
-        protected CharsetEncoder getCharsetEncoder(ContentTypeField contentType) {
-            return ignoreEncoder(CharsetUtil.DEFAULT_CHARSET);
-        }
-    };
-
-    /**
-     * A message writer that uses the charset of the Content-Type header for
-     * encoding.
-     */
-    public static final MessageWriter LENIENT = new MessageWriter() {
-        @Override
-        protected CharsetEncoder getCharsetEncoder(ContentTypeField contentType) {
-            if (contentType == null) {
-                return ignoreEncoder(LENIENT_FALLBACK_CHARSET);
-            } else {
-                String charset = contentType.getCharset();
-                if (charset != null) {
-                    return ignoreEncoder(CharsetUtil.getCharset(charset));
-                } else {
-                    return ignoreEncoder(LENIENT_FALLBACK_CHARSET);
-                }
-            }
-        }
-    };
+    public static final MessageWriter DEFAULT = new MessageWriter();
 
     /**
      * Protected constructor prevents direct instantiation.
@@ -107,11 +66,8 @@
      *            the OutputStream to write to.
      * @throws IOException
      *             if an I/O error occurs.
-     * @throws MimeIOException
-     *             in case of a MIME protocol violation
      */
-    public void writeBody(Body body, OutputStream out) throws IOException,
-            MimeIOException {
+    public void writeBody(Body body, OutputStream out) throws IOException {
         if (body instanceof Message) {
             writeEntity((Message) body, out);
         } else if (body instanceof Multipart) {
@@ -120,8 +76,6 @@
             ((SingleBody) body).writeTo(out);
         } else
             throw new IllegalArgumentException("Unsupported body class");
-
-        out.flush();
     }
 
     /**
@@ -134,17 +88,13 @@
      *            the OutputStream to write to.
      * @throws IOException
      *             if an I/O error occurs.
-     * @throws MimeIOException
-     *             in case of a MIME protocol violation
      */
-    public void writeEntity(Entity entity, OutputStream out)
-            throws IOException, MimeIOException {
+    public void writeEntity(Entity entity, OutputStream out) throws IOException {
         final Header header = entity.getHeader();
         if (header == null)
             throw new IllegalArgumentException("Missing header");
 
         writeHeader(header, out);
-        out.flush();
 
         final Body body = entity.getBody();
         if (body == null)
@@ -171,42 +121,31 @@
      *            the OutputStream to write to.
      * @throws IOException
      *             if an I/O error occurs.
-     * @throws MimeIOException
-     *             in case of a MIME protocol violation
      */
     public void writeMultipart(Multipart multipart, OutputStream out)
-            throws IOException, MimeIOException {
+            throws IOException {
         ContentTypeField contentType = getContentType(multipart);
 
-        String boundary = getBoundary(contentType);
+        ByteSequence boundary = getBoundary(contentType);
 
-        Writer writer = getWriter(contentType, out);
+        writeBytes(multipart.getPreambleRaw(), out);
+        out.write(CRLF);
 
-        try {
-            writer.write(multipart.getPreamble());
-            writer.write(CRLF);
-
-            for (BodyPart bodyPart : multipart.getBodyParts()) {
-                writer.write("--");
-                writer.write(boundary);
-                writer.write(CRLF);
-                writer.flush();
-
-                writeEntity(bodyPart, out);
-                writer.write(CRLF);
-            }
-
-            writer.write("--");
-            writer.write(boundary);
-            writer.write("--");
-            writer.write(CRLF);
-
-            writer.write(multipart.getEpilogue());
-
-            writer.flush();
-        } catch (CharacterCodingException e) {
-            throw new MimeIOException("Multipart violates RFC 822");
+        for (BodyPart bodyPart : multipart.getBodyParts()) {
+            out.write(DASHES);
+            writeBytes(boundary, out);
+            out.write(CRLF);
+
+            writeEntity(bodyPart, out);
+            out.write(CRLF);
         }
+
+        out.write(DASHES);
+        writeBytes(boundary, out);
+        out.write(DASHES);
+        out.write(CRLF);
+
+        writeBytes(multipart.getEpilogueRaw(), out);
     }
 
     /**
@@ -219,37 +158,17 @@
      *            the OutputStream to write to.
      * @throws IOException
      *             if an I/O error occurs.
-     * @throws MimeIOException
-     *             in case of a MIME protocol violation
      */
-    public void writeHeader(Header header, OutputStream out)
-            throws IOException, MimeIOException {
-        Writer writer = getWriter((ContentTypeField) header
-                .getField(FieldName.CONTENT_TYPE), out);
-
-        try {
-            for (Field field : header) {
-                writer.write(field.getRaw());
-                writer.write(CRLF);
-            }
-
-            writer.write(CRLF);
-            writer.flush();
-        } catch (CharacterCodingException e) {
-            throw new MimeIOException("Header violates RFC 822");
+    public void writeHeader(Header header, OutputStream out) throws IOException {
+        for (Field field : header) {
+            writeBytes(field.getRaw(), out);
+            out.write(CRLF);
         }
-    }
 
-    CharsetEncoder getCharsetEncoder(ContentTypeField contentType) {
-        return CharsetUtil.DEFAULT_CHARSET.newEncoder();
+        out.write(CRLF);
     }
 
-    CharsetEncoder ignoreEncoder(Charset charset) {
-        return charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE)
-                .onUnmappableCharacter(CodingErrorAction.REPLACE);
-    }
-
-    private OutputStream encodeStream(OutputStream out, String encoding,
+    protected OutputStream encodeStream(OutputStream out, String encoding,
             boolean binaryBody) throws IOException {
         if (MimeUtil.isBase64Encoding(encoding)) {
             return CodecUtil.wrapBase64(out);
@@ -280,20 +199,23 @@
         return contentType;
     }
 
-    private String getBoundary(ContentTypeField contentType) {
+    private ByteSequence getBoundary(ContentTypeField contentType) {
         String boundary = contentType.getBoundary();
         if (boundary == null)
             throw new IllegalArgumentException(
                     "Multipart boundary not specified");
 
-        return boundary;
+        return ContentUtil.encode(boundary);
     }
 
-    private Writer getWriter(ContentTypeField contentType, OutputStream out) {
-        CharsetEncoder encoder = getCharsetEncoder(contentType);
-
-        return new BufferedWriter(new OutputStreamWriter(out, encoder),
-                WRITER_BUFFER_SIZE);
+    private void writeBytes(ByteSequence byteSequence, OutputStream out)
+            throws IOException {
+        if (byteSequence instanceof ByteArrayBuffer) {
+            ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence;
+            out.write(bab.buffer(), 0, bab.length());
+        } else {
+            out.write(byteSequence.toByteArray());
+        }
     }
 
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java Fri Feb 27 16:45:02 2009
@@ -23,24 +23,37 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.ContentUtil;
+
 /**
- * Represents a MIME multipart body (see RFC 2045).A multipart body has a 
+ * Represents a MIME multipart body (see RFC 2045).A multipart body has a
  * ordered list of body parts. The multipart body also has a preamble and
- * epilogue. The preamble consists of whatever characters appear before the 
- * first body part while the epilogue consists of whatever characters come
- * after the last body part.
+ * epilogue. The preamble consists of whatever characters appear before the
+ * first body part while the epilogue consists of whatever characters come after
+ * the last body part.
  */
 public class Multipart implements Body {
-    private String preamble = "";
-    private String epilogue = "";
+
     private List<BodyPart> bodyParts = new LinkedList<BodyPart>();
     private Entity parent = null;
+
+    private ByteSequence preamble;
+    private transient String preambleStrCache;
+    private ByteSequence epilogue;
+    private transient String epilogueStrCache;
+
     private String subType;
 
     /**
      * Creates a new empty <code>Multipart</code> instance.
      */
     public Multipart(String subType) {
+        preamble = ByteSequence.EMPTY;
+        preambleStrCache = "";
+        epilogue = ByteSequence.EMPTY;
+        epilogueStrCache = "";
+
         this.subType = subType;
     }
 
@@ -64,45 +77,48 @@
      */
     public Multipart(Multipart other) {
         preamble = other.preamble;
+        preambleStrCache = other.preambleStrCache;
         epilogue = other.epilogue;
-        
+        epilogueStrCache = other.epilogueStrCache;
+
         for (BodyPart otherBodyPart : other.bodyParts) {
             BodyPart bodyPartCopy = new BodyPart(otherBodyPart);
             addBodyPart(bodyPartCopy);
         }
-        
+
         subType = other.subType;
     }
 
     /**
-     * Gets the multipart sub-type. E.g. <code>alternative</code> (the default)
-     * or <code>parallel</code>. See RFC 2045 for common sub-types and their
-     * meaning.
+     * Gets the multipart sub-type. E.g. <code>alternative</code> (the
+     * default) or <code>parallel</code>. See RFC 2045 for common sub-types
+     * and their meaning.
      * 
      * @return the multipart sub-type.
      */
     public String getSubType() {
         return subType;
     }
-    
+
     /**
-     * Sets the multipart sub-type. E.g. <code>alternative</code>
-     * or <code>parallel</code>. See RFC 2045 for common sub-types and their
+     * Sets the multipart sub-type. E.g. <code>alternative</code> or
+     * <code>parallel</code>. See RFC 2045 for common sub-types and their
      * meaning.
      * 
-     * @param subType the sub-type.
+     * @param subType
+     *            the sub-type.
      */
     public void setSubType(String subType) {
         this.subType = subType;
     }
-    
+
     /**
      * @see org.apache.james.mime4j.message.Body#getParent()
      */
     public Entity getParent() {
         return parent;
     }
-    
+
     /**
      * @see org.apache.james.mime4j.message.Body#setParent(org.apache.james.mime4j.message.Entity)
      */
@@ -114,24 +130,6 @@
     }
 
     /**
-     * Gets the epilogue.
-     * 
-     * @return the epilogue.
-     */
-    public String getEpilogue() {
-        return epilogue;
-    }
-    
-    /**
-     * Sets the epilogue.
-     * 
-     * @param epilogue the epilogue.
-     */
-    public void setEpilogue(String epilogue) {
-        this.epilogue = epilogue;
-    }
-    
-    /**
      * Returns the number of body parts.
      * 
      * @return number of <code>BodyPart</code> objects.
@@ -139,7 +137,7 @@
     public int getCount() {
         return bodyParts.size();
     }
-    
+
     /**
      * Gets the list of body parts. The list is immutable.
      * 
@@ -148,11 +146,12 @@
     public List<BodyPart> getBodyParts() {
         return Collections.unmodifiableList(bodyParts);
     }
-    
+
     /**
      * Sets the list of body parts.
      * 
-     * @param bodyParts the new list of <code>BodyPart</code> objects.
+     * @param bodyParts
+     *            the new list of <code>BodyPart</code> objects.
      */
     public void setBodyParts(List<BodyPart> bodyParts) {
         this.bodyParts = bodyParts;
@@ -160,20 +159,21 @@
             bodyPart.setParent(parent);
         }
     }
-    
+
     /**
      * Adds a body part to the end of the list of body parts.
      * 
-     * @param bodyPart the body part.
+     * @param bodyPart
+     *            the body part.
      */
     public void addBodyPart(BodyPart bodyPart) {
         if (bodyPart == null)
             throw new IllegalArgumentException();
-        
+
         bodyParts.add(bodyPart);
         bodyPart.setParent(parent);
     }
-    
+
     /**
      * Inserts a body part at the specified position in the list of body parts.
      * 
@@ -188,11 +188,11 @@
     public void addBodyPart(BodyPart bodyPart, int index) {
         if (bodyPart == null)
             throw new IllegalArgumentException();
-        
+
         bodyParts.add(index, bodyPart);
         bodyPart.setParent(parent);
     }
-    
+
     /**
      * Removes the body part at the specified position in the list of body
      * parts.
@@ -209,7 +209,7 @@
         bodyPart.setParent(null);
         return bodyPart;
     }
-    
+
     /**
      * Replaces the body part at the specified position in the list of body
      * parts with the specified body part.
@@ -229,30 +229,79 @@
 
         BodyPart replacedBodyPart = bodyParts.set(index, bodyPart);
         if (bodyPart == replacedBodyPart)
-            throw new IllegalArgumentException("Cannot replace body part with itself");
+            throw new IllegalArgumentException(
+                    "Cannot replace body part with itself");
 
         bodyPart.setParent(parent);
         replacedBodyPart.setParent(null);
 
         return replacedBodyPart;
     }
-    
+
+    // package private for now; might become public someday
+    ByteSequence getPreambleRaw() {
+        return preamble;
+    }
+
+    void setPreambleRaw(ByteSequence preamble) {
+        this.preamble = preamble;
+        this.preambleStrCache = null;
+    }
+
     /**
      * Gets the preamble.
      * 
      * @return the preamble.
      */
     public String getPreamble() {
-        return preamble;
+        if (preambleStrCache == null) {
+            preambleStrCache = ContentUtil.decode(preamble);
+        }
+        return preambleStrCache;
     }
-    
+
     /**
      * Sets the preamble.
      * 
-     * @param preamble the preamble.
+     * @param preamble
+     *            the preamble.
      */
     public void setPreamble(String preamble) {
-        this.preamble = preamble;
+        this.preamble = ContentUtil.encode(preamble);
+        this.preambleStrCache = preamble;
+    }
+
+    // package private for now; might become public someday
+    ByteSequence getEpilogueRaw() {
+        return epilogue;
+    }
+
+    void setEpilogueRaw(ByteSequence epilogue) {
+        this.epilogue = epilogue;
+        this.epilogueStrCache = null;
+    }
+
+    /**
+     * Gets the epilogue.
+     * 
+     * @return the epilogue.
+     */
+    public String getEpilogue() {
+        if (epilogueStrCache == null) {
+            epilogueStrCache = ContentUtil.decode(epilogue);
+        }
+        return epilogueStrCache;
+    }
+
+    /**
+     * Sets the epilogue.
+     * 
+     * @param epilogue
+     *            the epilogue.
+     */
+    public void setEpilogue(String epilogue) {
+        this.epilogue = ContentUtil.encode(epilogue);
+        this.epilogueStrCache = epilogue;
     }
 
     /**

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java Fri Feb 27 16:45:02 2009
@@ -33,7 +33,6 @@
 import org.apache.james.mime4j.io.MaxHeaderLimitException;
 import org.apache.james.mime4j.io.MaxLineLimitException;
 import org.apache.james.mime4j.util.ByteArrayBuffer;
-import org.apache.james.mime4j.util.CharArrayBuffer;
 import org.apache.james.mime4j.util.CharsetUtil;
 
 /**
@@ -52,7 +51,6 @@
     protected int state;
 
     private final ByteArrayBuffer linebuf;
-    private final CharArrayBuffer fieldbuf;
 
     private int lineCount;
     private Field field;
@@ -92,7 +90,6 @@
         this.config = config;
         this.body = newBodyDescriptor(parent);
         this.linebuf = new ByteArrayBuffer(64);
-        this.fieldbuf = new CharArrayBuffer(64);
         this.lineCount = 0;
         this.endOfHeader = false;
         this.headerCount = 0;
@@ -125,13 +122,14 @@
     
     protected abstract LineReaderInputStream getDataStream();
     
-    private void fillFieldBuffer() throws IOException, MimeException {
-        if (endOfHeader) {
-            return;
-        }
+    private ByteArrayBuffer fillFieldBuffer() throws IOException, MimeException {
+        if (endOfHeader) 
+            throw new IllegalStateException();
+
         int maxLineLen = config.getMaxLineLen();
         LineReaderInputStream instream = getDataStream();
-        fieldbuf.clear();
+        ByteArrayBuffer fieldbuf = new ByteArrayBuffer(64);
+
         for (;;) {
             // If there's still data stuck in the line buffer
             // copy it to the field buffer
@@ -140,7 +138,7 @@
                 throw new MaxLineLimitException("Maximum line length limit exceeded");
             }
             if (len > 0) {
-                fieldbuf.append(linebuf, 0, len);
+                fieldbuf.append(linebuf.buffer(), 0, len);
             }
             linebuf.clear();
             if (instream.readLine(linebuf) == -1) {
@@ -169,6 +167,8 @@
                 }
             }
         }
+
+        return fieldbuf;
     }
 
     protected boolean parseField() throws MimeException, IOException {
@@ -180,42 +180,37 @@
             if (headerCount >= maxHeaderLimit) {
                 throw new MaxHeaderLimitException("Maximum header limit exceeded");
             }
-            
-            fillFieldBuffer();
+
+            ByteArrayBuffer fieldbuf = fillFieldBuffer();
             headerCount++;
-            
+
             // Strip away line delimiter
             int len = fieldbuf.length();
-            if (len > 0 && fieldbuf.charAt(len - 1) == '\n') {
+            if (len > 0 && fieldbuf.byteAt(len - 1) == '\n') {
                 len--;
             }
-            if (len > 0 && fieldbuf.charAt(len - 1) == '\r') {
+            if (len > 0 && fieldbuf.byteAt(len - 1) == '\r') {
                 len--;
             }
             fieldbuf.setLength(len);
             
             boolean valid = true;
             
-            String fieldName = null;
-            String fieldValue = null;
-            
-            int pos = fieldbuf.indexOf(':');
+            int pos = fieldbuf.indexOf((byte) ':');
             if (pos <= 0) {
                 monitor(Event.INALID_HEADER);
                 valid = false;
             } else {
-                fieldName = fieldbuf.substring(0, pos);
-                for (int i = 0; i < fieldName.length(); i++) {
-                    if (!fieldChars.get(fieldName.charAt(i))) {
+                for (int i = 0; i < pos; i++) {
+                    if (!fieldChars.get(fieldbuf.byteAt(i) & 0xff)) {
                         monitor(Event.INALID_HEADER);
                         valid = false;
                         break;
                     }
                 }
-                fieldValue = fieldbuf.substring(pos + 1, fieldbuf.length());
             }
             if (valid) {
-                field = new RawField(fieldName, fieldValue, fieldbuf.toString());
+                field = new RawField(fieldbuf, pos);
                 body.addField(field);            
                 return true;
             }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java Fri Feb 27 16:45:02 2009
@@ -19,6 +19,8 @@
 
 package org.apache.james.mime4j.parser;
 
+import org.apache.james.mime4j.util.ByteSequence;
+
 /**
  * Abstract MIME field.
  */
@@ -39,10 +41,10 @@
     String getBody();
 
     /**
-     * Gets the original raw field string.
+     * Gets the original raw field bytes.
      * 
-     * @return the original raw field string.
+     * @return the original raw field bytes.
      */
-    String getRaw();
+    ByteSequence getRaw();
     
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java Fri Feb 27 16:45:02 2009
@@ -32,6 +32,8 @@
 import org.apache.james.mime4j.io.LineReaderInputStream;
 import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor;
 import org.apache.james.mime4j.io.MimeBoundaryInputStream;
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.ContentUtil;
 import org.apache.james.mime4j.util.MimeUtil;
 
 public class MimeEntity extends AbstractEntity {
@@ -94,12 +96,10 @@
             throw new IllegalStateException("Invalid state: " + stateToString(state));
         }
         skipHeader = true;
-        body.addField(new RawField(
-                "Content-Type", 
-                contentType,
-                "Content-Type: " +contentType));
+        ByteSequence raw = ContentUtil.encode("Content-Type: " + contentType);
+        body.addField(new RawField(raw, 12));
     }
-    
+
     @Override
     protected int getLineNumber() {
         if (lineSource == null)

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java Fri Feb 27 16:45:02 2009
@@ -19,37 +19,58 @@
 
 package org.apache.james.mime4j.parser;
 
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.mime4j.util.ContentUtil;
+
 /**
  * The basic immutable MIME field.
  */
-public class RawField implements Field {
+class RawField implements Field {
+
+    private final ByteSequence raw;
+    private int colonIdx;
+
+    private String name;
+    private String body;
 
-    private final String name;
-    private final String body;
-    private final String raw;
-
-    public RawField(String name, String body, String raw) {
-        super();
-        this.name = name;
-        this.body = body;
+    public RawField(ByteSequence raw, int colonIdx) {
         this.raw = raw;
+        this.colonIdx = colonIdx;
     }
 
     public String getName() {
-        return this.name;
+        if (name == null) {
+            name = parseName();
+        }
+
+        return name;
     }
 
     public String getBody() {
-        return this.body;
+        if (body == null) {
+            body = parseBody();
+        }
+
+        return body;
     }
 
-    public String getRaw() {
-        return this.raw;
+    public ByteSequence getRaw() {
+        return raw;
     }
 
     @Override
     public String toString() {
-        return this.raw;
+        return getName() + ':' + getBody();
+    }
+
+    private String parseName() {
+        return ContentUtil.decode(raw, 0, colonIdx);
+    }
+
+    private String parseBody() {
+        int offset = colonIdx + 1;
+        int length = raw.length() - offset;
+        return ContentUtil.decode(raw, offset, length);
     }
 
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java Fri Feb 27 16:45:02 2009
@@ -19,10 +19,11 @@
 
 package org.apache.james.mime4j.util;
 
+
 /**
  * A resizable byte array.
  */
-public final class ByteArrayBuffer  {
+public final class ByteArrayBuffer implements ByteSequence {
     
     private byte[] buffer;
     private int len;
@@ -35,6 +36,26 @@
         this.buffer = new byte[capacity]; 
     }
 
+    public ByteArrayBuffer(byte[] bytes, boolean dontCopy) {
+        this(bytes, bytes.length, dontCopy);
+    }
+    
+    public ByteArrayBuffer(byte[] bytes, int len, boolean dontCopy) {
+        if (bytes == null)
+            throw new IllegalArgumentException();
+        if (len < 0 || len > bytes.length)
+            throw new IllegalArgumentException();
+
+        if (dontCopy) {
+            this.buffer = bytes;
+        } else {
+            this.buffer = new byte[len];
+            System.arraycopy(bytes, 0, this.buffer, 0, len);
+        }
+
+        this.len = len;
+    }
+
     private void expand(int newlen) {
         byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)];
         System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
@@ -103,7 +124,10 @@
         return b;
     }
     
-    public int byteAt(int i) {
+    public byte byteAt(int i) {
+        if (i < 0 || i >= this.len)
+            throw new IndexOutOfBoundsException();
+
         return this.buffer[i];
     }
     
@@ -118,7 +142,29 @@
     public byte[] buffer() {
         return this.buffer;
     }
-        
+
+    public int indexOf(byte b) {
+        return indexOf(b, 0, this.len);
+    }
+
+    public int indexOf(byte b, int beginIndex, int endIndex) {
+        if (beginIndex < 0) {
+            beginIndex = 0;
+        }
+        if (endIndex > this.len) {
+            endIndex = this.len;
+        }
+        if (beginIndex > endIndex) {
+            return -1;
+        }
+        for (int i = beginIndex; i < endIndex; i++) {
+            if (this.buffer[i] == b) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     public void setLength(int len) {
         if (len < 0 || len > this.buffer.length) {
             throw new IndexOutOfBoundsException();
@@ -138,5 +184,5 @@
     public String toString() {
         return new String(toByteArray());
     }
-    
+
 }

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java?rev=748583&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java Fri Feb 27 16:45:02 2009
@@ -0,0 +1,58 @@
+/****************************************************************
+ * 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 org.apache.james.mime4j.util;
+
+/**
+ * An immutable sequence of bytes.
+ */
+public interface ByteSequence {
+
+    /**
+     * An empty byte sequence.
+     */
+    ByteSequence EMPTY = new EmptyByteSequence();
+
+    /**
+     * Returns the length of this byte sequence.
+     * 
+     * @return the number of <code>byte</code>s in this sequence.
+     */
+    int length();
+
+    /**
+     * Returns the <code>byte</code> value at the specified index.
+     * 
+     * @param index
+     *            the index of the <code>byte</code> value to be returned.
+     * @return the corresponding <code>byte</code> value
+     * @throws IndexOutOfBoundsException
+     *             if <code>index < 0 || index >= length()</code>.
+     */
+    byte byteAt(int index);
+
+    /**
+     * Copies the contents of this byte sequence into a newly allocated byte
+     * array and returns that array.
+     * 
+     * @return a byte array holding a copy of this byte sequence.
+     */
+    byte[] toByteArray();
+
+}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java?rev=748583&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java Fri Feb 27 16:45:02 2009
@@ -0,0 +1,138 @@
+/****************************************************************
+ * 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 org.apache.james.mime4j.util;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * Utility methods for converting textual content of a message.
+ */
+public class ContentUtil {
+
+    private ContentUtil() {
+    }
+
+    /**
+     * Encodes the specified string into an immutable sequence of bytes using
+     * the US-ASCII charset.
+     * 
+     * @param string
+     *            string to encode.
+     * @return encoded string as an immutable sequence of bytes.
+     */
+    public static ByteSequence encode(String string) {
+        return encode(CharsetUtil.US_ASCII, string);
+    }
+
+    /**
+     * Encodes the specified string into an immutable sequence of bytes using
+     * the specified charset.
+     * 
+     * @param charset
+     *            Java charset to be used for the conversion.
+     * @param string
+     *            string to encode.
+     * @return encoded string as an immutable sequence of bytes.
+     */
+    public static ByteSequence encode(Charset charset, String string) {
+        ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
+        ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
+        bab.append(encoded.array(), encoded.position(), encoded.remaining());
+        return bab;
+    }
+
+    /**
+     * Decodes the specified sequence of bytes into a string using the US-ASCII
+     * charset.
+     * 
+     * @param byteSequence
+     *            sequence of bytes to decode.
+     * @return decoded string.
+     */
+    public static String decode(ByteSequence byteSequence) {
+        return decode(CharsetUtil.US_ASCII, byteSequence, 0, byteSequence
+                .length());
+    }
+
+    /**
+     * Decodes the specified sequence of bytes into a string using the specified
+     * charset.
+     * 
+     * @param charset
+     *            Java charset to be used for the conversion.
+     * @param byteSequence
+     *            sequence of bytes to decode.
+     * @return decoded string.
+     */
+    public static String decode(Charset charset, ByteSequence byteSequence) {
+        return decode(charset, byteSequence, 0, byteSequence.length());
+    }
+
+    /**
+     * Decodes a sub-sequence of the specified sequence of bytes into a string
+     * using the US-ASCII charset.
+     * 
+     * @param byteSequence
+     *            sequence of bytes to decode.
+     * @param offset
+     *            offset into the byte sequence.
+     * @param length
+     *            number of bytes.
+     * @return decoded string.
+     */
+    public static String decode(ByteSequence byteSequence, int offset,
+            int length) {
+        return decode(CharsetUtil.US_ASCII, byteSequence, offset, length);
+    }
+
+    /**
+     * Decodes a sub-sequence of the specified sequence of bytes into a string
+     * using the specified charset.
+     * 
+     * @param charset
+     *            Java charset to be used for the conversion.
+     * @param byteSequence
+     *            sequence of bytes to decode.
+     * @param offset
+     *            offset into the byte sequence.
+     * @param length
+     *            number of bytes.
+     * @return decoded string.
+     */
+    public static String decode(Charset charset, ByteSequence byteSequence,
+            int offset, int length) {
+        if (byteSequence instanceof ByteArrayBuffer) {
+            ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence;
+            return decode(charset, bab.buffer(), offset, length);
+        } else {
+            byte[] bytes = byteSequence.toByteArray();
+            return decode(charset, bytes, offset, length);
+        }
+    }
+
+    private static String decode(Charset charset, byte[] buffer, int offset,
+            int length) {
+        return charset.decode(ByteBuffer.wrap(buffer, offset, length))
+                .toString();
+    }
+
+}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java?rev=748583&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java Fri Feb 27 16:45:02 2009
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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 org.apache.james.mime4j.util;
+
+final class EmptyByteSequence implements ByteSequence {
+    private static final byte[] EMPTY_BYTES = {};
+
+    public int length() {
+        return 0;
+    }
+
+    public byte byteAt(int index) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    public byte[] toByteArray() {
+        return EMPTY_BYTES;
+    }
+}
\ No newline at end of file

Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java?rev=748583&r1=748582&r2=748583&view=diff
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java (original)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java Fri Feb 27 16:45:02 2009
@@ -19,12 +19,11 @@
 
 package org.apache.james.mime4j.descriptor;
 
-import org.apache.james.mime4j.descriptor.BodyDescriptor;
-import org.apache.james.mime4j.descriptor.MutableBodyDescriptor;
-import org.apache.james.mime4j.field.AbstractField;
-
 import junit.framework.TestCase;
 
+import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.util.ByteSequence;
+
 public abstract class BaseTestForBodyDescriptors extends TestCase {
 
     protected abstract MutableBodyDescriptor newBodyDescriptor();
@@ -35,7 +34,7 @@
         MutableBodyDescriptor bd = null;
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/plain; charset=ISO-8859-1; "
+        bd.addField(new TestField("Content-Type ", "text/plain; charset=ISO-8859-1; "
                 + "boundary=foo; param1=value1; param2=value2; param3=value3"));
         assertEquals(3, bd.getContentTypeParameters().size());
         assertEquals("value1", bd.getContentTypeParameters().get("param1"));
@@ -43,7 +42,7 @@
         assertEquals("value3", bd.getContentTypeParameters().get("param3"));
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/plain; param1=value1; param2=value2;"
+        bd.addField(new TestField("Content-Type ", "text/plain; param1=value1; param2=value2;"
                      + " param3=value3"));
         assertEquals(3, bd.getContentTypeParameters().size());
         assertEquals("value1", bd.getContentTypeParameters().get("param1"));
@@ -51,7 +50,7 @@
         assertEquals("value3", bd.getContentTypeParameters().get("param3"));
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/plain; "
+        bd.addField(new TestField("Content-Type ", "text/plain; "
                 + "param1= \" value with\tspaces \" ; "
                 + "param2=\"\\\"value4 with escaped \\\" \\\"\";"));
         assertEquals(2, bd.getContentTypeParameters().size());
@@ -63,7 +62,7 @@
          * The parameter value should be \n\"
          */
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/plain; param=\"\\n\\\\\\\"\""));
+        bd.addField(new TestField("Content-Type ", "text/plain; param=\"\\n\\\\\\\"\""));
         assertEquals(1, bd.getContentTypeParameters().size());
         assertEquals("\\n\\\"", bd.getContentTypeParameters().get("param"));
     }
@@ -75,10 +74,10 @@
          * Make sure that only the first Content-Type header added is used.
          */
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/plain; charset=ISO-8859-1"));
+        bd.addField(new TestField("Content-Type ", "text/plain; charset=ISO-8859-1"));
         assertEquals("text/plain", bd.getMimeType());
         assertEquals("iso-8859-1", bd.getCharset());
-        bd.addField(AbstractField.parse("Content-Type ", "text/html; charset=us-ascii"));
+        bd.addField(new TestField("Content-Type ", "text/html; charset=us-ascii"));
         assertEquals("text/plain", bd.getMimeType());
         assertEquals("iso-8859-1", bd.getCharset());
     }
@@ -87,32 +86,32 @@
         MutableBodyDescriptor bd = null;
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/PLAIN"));
+        bd.addField(new TestField("Content-Type ", "text/PLAIN"));
         assertEquals("text/plain", bd.getMimeType());
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/PLAIN;"));
+        bd.addField(new TestField("Content-Type ", "text/PLAIN;"));
         assertEquals("text/plain", bd.getMimeType());
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("content-type", "   TeXt / html   "));
+        bd.addField(new TestField("content-type", "   TeXt / html   "));
         assertEquals("text/html", bd.getMimeType());
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("CONTENT-TYPE", "   x-app/yada ;  param = yada"));
+        bd.addField(new TestField("CONTENT-TYPE", "   x-app/yada ;  param = yada"));
         assertEquals("x-app/yada", bd.getMimeType());
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("CONTENT-TYPE", "   yada"));
+        bd.addField(new TestField("CONTENT-TYPE", "   yada"));
         assertEquals("text/plain", bd.getMimeType());
         
         /*
          * Make sure that only the first Content-Type header added is used.
          */
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type ", "text/plain"));
+        bd.addField(new TestField("Content-Type ", "text/plain"));
         assertEquals("text/plain", bd.getMimeType());
-        bd.addField(AbstractField.parse("Content-Type ", "text/html"));
+        bd.addField(new TestField("Content-Type ", "text/html"));
         assertEquals("text/plain", bd.getMimeType());
         
         /*
@@ -122,19 +121,19 @@
         MutableBodyDescriptor parent = null;
         
         parent = newBodyDescriptor();
-        parent.addField(AbstractField.parse("Content-Type", "mutlipart/alternative; boundary=foo"));
+        parent.addField(new TestField("Content-Type", "mutlipart/alternative; boundary=foo"));
         
         child = newBodyDescriptor(parent);
         assertEquals("text/plain", child.getMimeType());
-        child.addField(AbstractField.parse("Content-Type", " child/type"));
+        child.addField(new TestField("Content-Type", " child/type"));
         assertEquals("child/type", child.getMimeType());
         
         parent = newBodyDescriptor();
-        parent.addField(AbstractField.parse("Content-Type", "multipart/digest; boundary=foo"));
+        parent.addField(new TestField("Content-Type", "multipart/digest; boundary=foo"));
         
         child = newBodyDescriptor(parent);
         assertEquals("message/rfc822", child.getMimeType());
-        child.addField(AbstractField.parse("Content-Type", " child/type"));
+        child.addField(new TestField("Content-Type", " child/type"));
         assertEquals("child/type", child.getMimeType());
         
     }
@@ -147,39 +146,39 @@
          */
         bd = newBodyDescriptor();
         assertEquals("us-ascii", bd.getCharset());
-        bd.addField(AbstractField.parse("Content-Type ", "text/type; charset=ISO-8859-1"));
+        bd.addField(new TestField("Content-Type ", "text/type; charset=ISO-8859-1"));
         assertEquals("iso-8859-1", bd.getCharset());
         
         bd = newBodyDescriptor();
         assertEquals("us-ascii", bd.getCharset());
-        bd.addField(AbstractField.parse("Content-Type ", "text/type"));
+        bd.addField(new TestField("Content-Type ", "text/type"));
         assertEquals("us-ascii", bd.getCharset());
         
         /*
          * Test boundary.
          */
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type", "text/html; boundary=yada yada"));
+        bd.addField(new TestField("Content-Type", "text/html; boundary=yada yada"));
         assertNull(bd.getBoundary());
 
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boundary=yada"));
+        bd.addField(new TestField("Content-Type", "multipart/yada; boundary=yada"));
         assertEquals("yada", bd.getBoundary());
 
         /*
          * Test some weird parameters.
          */
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boundary=yada yada"));
+        bd.addField(new TestField("Content-Type", "multipart/yada; boundary=yada yada"));
         assertEquals("yada", bd.getBoundary());
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boUNdarY= ya:*da; \tcharset\t =  big5"));
+        bd.addField(new TestField("Content-Type", "multipart/yada; boUNdarY= ya:*da; \tcharset\t =  big5"));
         assertEquals("ya:*da", bd.getBoundary());
         assertEquals("big5", bd.getCharset());
         
         bd = newBodyDescriptor();
-        bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boUNdarY= \"ya \\\"\\\"\tda \\\"\"; "
+        bd.addField(new TestField("Content-Type", "multipart/yada; boUNdarY= \"ya \\\"\\\"\tda \\\"\"; "
                             + "\tcharset\t =  \"\\\"hepp\\\"  =us\t-ascii\""));
         assertEquals("ya \"\"\tda \"", bd.getBoundary());
         assertEquals("\"hepp\"  =us\t-ascii", bd.getCharset());
@@ -192,23 +191,47 @@
         bd = newBodyDescriptor();
         assertEquals(-1, bd.getContentLength());
 
-        bd.addField(AbstractField.parse("Content-Length", "9901"));
+        bd.addField(new TestField("Content-Length", "9901"));
         assertEquals(9901, bd.getContentLength());
 
         // only the first content-length counts
-        bd.addField(AbstractField.parse("Content-Length", "1239901"));
+        bd.addField(new TestField("Content-Length", "1239901"));
         assertEquals(9901, bd.getContentLength());
     }
     
     public void testDoDefaultToUsAsciiWhenUntyped() throws Exception {
         MutableBodyDescriptor descriptor = newBodyDescriptor();
-        descriptor.addField(AbstractField.parse("To", "me@example.org"));
+        descriptor.addField(new TestField("To", "me@example.org"));
         assertEquals("us-ascii", descriptor.getCharset());
     }
 
     public void testDoNotDefaultToUsAsciiForNonTextTypes() throws Exception {
         MutableBodyDescriptor descriptor = newBodyDescriptor();
-        descriptor.addField(AbstractField.parse("Content-Type", "image/png; name=blob.png"));
+        descriptor.addField(new TestField("Content-Type", "image/png; name=blob.png"));
         assertNull(descriptor.getCharset());
     }
+    
+    private static final class TestField implements Field {
+
+        private final String name;
+        private final String body;
+
+        public TestField(String name, String body){
+            this.name = name;
+            this.body = body;
+        }
+        
+        public String getName() {
+            return name;
+        }
+
+        public String getBody() {
+            return body;
+        }
+
+        public ByteSequence getRaw() {
+            throw new UnsupportedOperationException();
+        }
+        
+    }
 }