Index: src/test/java/org/apache/james/mime4j/MultipartTokensTest.java
===================================================================
--- src/test/java/org/apache/james/mime4j/MultipartTokensTest.java	(revision 674082)
+++ src/test/java/org/apache/james/mime4j/MultipartTokensTest.java	(working copy)
@@ -57,6 +57,7 @@
     MESSAGE +
     "\r\n" +
     "--42\r\n" +
+    "\r\n" +
     "Custard!" +
     "\r\n" +
     "--42--\r\n";
@@ -157,6 +158,29 @@
         checkState(MimeTokenStream.T_END_OF_STREAM);
     }
 
+    public void testMultipartMessageWithoutHeader() throws Exception {
+        parser.parseHeadless(new ByteArrayInputStream(US_ASCII.encode(BODY).array()), 
+                "multipart/mixed;boundary=1729");
+        checkState(MimeTokenStream.T_END_HEADER);
+        checkState(MimeTokenStream.T_START_MULTIPART);
+        checkState(MimeTokenStream.T_PREAMBLE);
+        checkState(MimeTokenStream.T_START_BODYPART);
+        checkState(MimeTokenStream.T_START_HEADER);
+        checkState(MimeTokenStream.T_END_HEADER);
+        checkState(MimeTokenStream.T_BODY);
+        checkState(MimeTokenStream.T_END_BODYPART);
+        checkState(MimeTokenStream.T_START_BODYPART);
+        checkState(MimeTokenStream.T_START_HEADER);
+        checkState(MimeTokenStream.T_FIELD);
+        checkState(MimeTokenStream.T_END_HEADER);
+        checkState(MimeTokenStream.T_BODY);
+        checkState(MimeTokenStream.T_END_BODYPART);
+        checkState(MimeTokenStream.T_EPILOGUE);
+        checkState(MimeTokenStream.T_END_MULTIPART);
+        checkState(MimeTokenStream.T_END_MESSAGE);
+        checkState(MimeTokenStream.T_END_OF_STREAM);
+    }
+    
     private void checkState(final int state) throws IOException, MimeException {
         assertEquals(MimeTokenStream.stateToString(state), MimeTokenStream.stateToString(parser.next()));
     }
Index: src/test/java/org/apache/james/mime4j/MimeEntityTest.java
===================================================================
--- src/test/java/org/apache/james/mime4j/MimeEntityTest.java	(revision 0)
+++ src/test/java/org/apache/james/mime4j/MimeEntityTest.java	(revision 0)
@@ -0,0 +1,332 @@
+/****************************************************************
+ * 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;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.commons.io.IOUtils;
+
+import junit.framework.TestCase;
+
+public class MimeEntityTest extends TestCase {
+
+    public void testSimpleEntity() throws Exception {
+        String message = 
+            "To: Road Runner <runner@example.org>\r\n" +
+            "From: Wile E. Cayote <wile@example.org>\r\n" +
+            "Date: Tue, 12 Feb 2008 17:34:09 +0000 (GMT)\r\n" +
+            "Subject: Mail\r\n" +
+            "Content-Type: text/plain\r\n" +
+            "\r\n" +
+            "a very important message";
+        byte[] raw = message.getBytes("US-ASCII");
+        ByteArrayInputStream instream = new ByteArrayInputStream(raw);
+        RootInputStream rootStream = new RootInputStream(instream); 
+        InputBuffer inbuffer = new InputBuffer(rootStream, 12); 
+        BufferingInputStream rawstream = new BufferingInputStream(inbuffer); 
+        
+        MimeEntity entity = new MimeEntity(
+                rootStream,
+                rawstream,
+                inbuffer,
+                null,
+                EntityStates.T_START_MESSAGE,
+                EntityStates.T_END_MESSAGE);
+        
+        
+        assertEquals(EntityStates.T_START_MESSAGE, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_START_HEADER, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("To", entity.getFieldName());
+        assertEquals(" Road Runner <runner@example.org>", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("From", entity.getFieldName());
+        assertEquals(" Wile E. Cayote <wile@example.org>", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Date", entity.getFieldName());
+        assertEquals(" Tue, 12 Feb 2008 17:34:09 +0000 (GMT)", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Subject", entity.getFieldName());
+        assertEquals(" Mail", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Content-Type", entity.getFieldName());
+        assertEquals(" text/plain", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_END_HEADER, entity.getState());
+        try {
+            entity.getFieldName();
+            fail("IllegalStateException should have been thrown");
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            entity.getFieldValue();
+            fail("IllegalStateException should have been thrown");
+        } catch (IllegalStateException expected) {
+        }
+        
+        entity.advance();
+        assertEquals(EntityStates.T_BODY, entity.getState());
+        assertEquals("a very important message", IOUtils.toString(entity.getContentStream()));
+        entity.advance();
+        assertEquals(EntityStates.T_END_MESSAGE, entity.getState());
+        try {
+            entity.getContentStream();
+            fail("IllegalStateException should have been thrown");
+        } catch (IllegalStateException expected) {
+        }
+        entity.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, entity.getState());
+        try {
+            entity.advance();
+            fail("IllegalStateException should have been thrown");
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testMultipartEntity() throws Exception {
+        String message = 
+            "To: Road Runner <runner@example.org>\r\n" +
+            "From: Wile E. Cayote <wile@example.org>\r\n" +
+            "Date: Tue, 12 Feb 2008 17:34:09 +0000 (GMT)\r\n" +
+            "Subject: Mail\r\n" +
+            "Content-Type: multipart/mixed;boundary=1729\r\n" +
+            "\r\n" +
+            "Hello!\r\n" +
+            "--1729\r\n" +
+            "Content-Type: text/plain; charset=US-ASCII\r\n" +
+            "\r\n" +
+            "blah blah blah\r\n" +
+            "--1729\r\n" +
+            "Content-Type: text/plain; charset=US-ASCII\r\n" +
+            "\r\n" +
+            "yada yada yada\r\n" +
+            "--1729--\r\n" +
+            "Goodbye!";
+        byte[] raw = message.getBytes("US-ASCII");
+        ByteArrayInputStream instream = new ByteArrayInputStream(raw);
+        RootInputStream rootStream = new RootInputStream(instream); 
+        InputBuffer inbuffer = new InputBuffer(rootStream, 24); 
+        BufferingInputStream rawstream = new BufferingInputStream(inbuffer); 
+        
+        MimeEntity entity = new MimeEntity(
+                rootStream,
+                rawstream,
+                inbuffer,
+                null,
+                EntityStates.T_START_MESSAGE,
+                EntityStates.T_END_MESSAGE);
+        
+        assertEquals(EntityStates.T_START_MESSAGE, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_START_HEADER, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("To", entity.getFieldName());
+        assertEquals(" Road Runner <runner@example.org>", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("From", entity.getFieldName());
+        assertEquals(" Wile E. Cayote <wile@example.org>", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Date", entity.getFieldName());
+        assertEquals(" Tue, 12 Feb 2008 17:34:09 +0000 (GMT)", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Subject", entity.getFieldName());
+        assertEquals(" Mail", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Content-Type", entity.getFieldName());
+        assertEquals(" multipart/mixed;boundary=1729", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_END_HEADER, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_START_MULTIPART, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_PREAMBLE, entity.getState());
+        assertEquals("Hello!", IOUtils.toString(entity.getContentStream()));
+        
+        EntityStateMachine p1 = entity.advance();
+        assertNotNull(p1);
+        
+        assertEquals(EntityStates.T_START_BODYPART, p1.getState());
+        p1.advance();
+        assertEquals(EntityStates.T_START_HEADER, p1.getState());
+        p1.advance();
+        assertEquals(EntityStates.T_FIELD, p1.getState());
+        assertEquals("Content-Type", p1.getFieldName());
+        assertEquals(" text/plain; charset=US-ASCII", p1.getFieldValue());
+        p1.advance();
+        assertEquals(EntityStates.T_END_HEADER, p1.getState());
+        p1.advance();
+        assertEquals(EntityStates.T_BODY, p1.getState());
+        assertEquals("blah blah blah", IOUtils.toString(p1.getContentStream()));
+        p1.advance();
+        assertEquals(EntityStates.T_END_BODYPART, p1.getState());
+        p1.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, p1.getState());
+
+        EntityStateMachine p2 = entity.advance();
+        assertNotNull(p2);
+        
+        assertEquals(EntityStates.T_START_BODYPART, p2.getState());
+        p2.advance();
+        assertEquals(EntityStates.T_START_HEADER, p2.getState());
+        p2.advance();
+        assertEquals(EntityStates.T_FIELD, p2.getState());
+        assertEquals("Content-Type", p2.getFieldName());
+        assertEquals(" text/plain; charset=US-ASCII", p2.getFieldValue());
+        p2.advance();
+        assertEquals(EntityStates.T_END_HEADER, p2.getState());
+        p2.advance();
+        assertEquals(EntityStates.T_BODY, p2.getState());
+        assertEquals("yada yada yada", IOUtils.toString(p2.getContentStream()));
+        p2.advance();
+        assertEquals(EntityStates.T_END_BODYPART, p2.getState());
+        p2.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, p2.getState());
+
+        entity.advance();
+        assertEquals(EntityStates.T_EPILOGUE, entity.getState());
+        assertEquals("Goodbye!", IOUtils.toString(entity.getContentStream()));
+        entity.advance();
+        assertEquals(EntityStates.T_END_MULTIPART, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_END_MESSAGE, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, entity.getState());
+    }
+    
+    public void testRawEntity() throws Exception {
+        String message = 
+            "To: Road Runner <runner@example.org>\r\n" +
+            "From: Wile E. Cayote <wile@example.org>\r\n" +
+            "Date: Tue, 12 Feb 2008 17:34:09 +0000 (GMT)\r\n" +
+            "Subject: Mail\r\n" +
+            "Content-Type: multipart/mixed;boundary=1729\r\n" +
+            "\r\n" +
+            "Hello!\r\n" +
+            "--1729\r\n" +
+            "Content-Type: text/plain; charset=US-ASCII\r\n" +
+            "\r\n" +
+            "blah blah blah\r\n" +
+            "--1729\r\n" +
+            "Content-Type: text/plain; charset=US-ASCII\r\n" +
+            "\r\n" +
+            "yada yada yada\r\n" +
+            "--1729--\r\n" +
+            "Goodbye!";
+        byte[] raw = message.getBytes("US-ASCII");
+        ByteArrayInputStream instream = new ByteArrayInputStream(raw);
+        RootInputStream rootStream = new RootInputStream(instream); 
+        InputBuffer inbuffer = new InputBuffer(rootStream, 24); 
+        BufferingInputStream rawstream = new BufferingInputStream(inbuffer); 
+        
+        MimeEntity entity = new MimeEntity(
+                rootStream,
+                rawstream,
+                inbuffer,
+                null,
+                EntityStates.T_START_MESSAGE,
+                EntityStates.T_END_MESSAGE);
+        
+        entity.setRecursionMode(RecursionMode.M_RAW);
+        
+        assertEquals(EntityStates.T_START_MESSAGE, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_START_HEADER, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("To", entity.getFieldName());
+        assertEquals(" Road Runner <runner@example.org>", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("From", entity.getFieldName());
+        assertEquals(" Wile E. Cayote <wile@example.org>", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Date", entity.getFieldName());
+        assertEquals(" Tue, 12 Feb 2008 17:34:09 +0000 (GMT)", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Subject", entity.getFieldName());
+        assertEquals(" Mail", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_FIELD, entity.getState());
+        assertEquals("Content-Type", entity.getFieldName());
+        assertEquals(" multipart/mixed;boundary=1729", entity.getFieldValue());
+        entity.advance();
+        assertEquals(EntityStates.T_END_HEADER, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_START_MULTIPART, entity.getState());
+        
+        entity.advance();
+        assertEquals(EntityStates.T_PREAMBLE, entity.getState());
+        assertEquals("Hello!", IOUtils.toString(entity.getContentStream()));
+        
+        EntityStateMachine p1 = entity.advance();
+        assertNotNull(p1);
+        
+        assertEquals(EntityStates.T_RAW_ENTITY, p1.getState());
+        assertNull(p1.getBodyDescriptor());
+        assertNull(p1.getField());
+        assertNull(p1.getFieldName());
+        assertNull(p1.getFieldValue());
+        assertEquals(
+                "Content-Type: text/plain; charset=US-ASCII\r\n" +
+                "\r\n" +
+                "blah blah blah", IOUtils.toString(p1.getContentStream()));
+        p1.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, p1.getState());
+
+        EntityStateMachine p2 = entity.advance();
+        assertNotNull(p2);
+        
+        assertEquals(EntityStates.T_RAW_ENTITY, p2.getState());
+        assertNull(p2.getBodyDescriptor());
+        assertNull(p2.getField());
+        assertNull(p2.getFieldName());
+        assertNull(p2.getFieldValue());
+        assertEquals(
+                "Content-Type: text/plain; charset=US-ASCII\r\n" +
+                "\r\n" +
+                "yada yada yada", IOUtils.toString(p2.getContentStream()));
+        p2.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, p2.getState());
+
+        entity.advance();
+        assertEquals(EntityStates.T_EPILOGUE, entity.getState());
+        assertEquals("Goodbye!", IOUtils.toString(entity.getContentStream()));
+        entity.advance();
+        assertEquals(EntityStates.T_END_MULTIPART, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_END_MESSAGE, entity.getState());
+        entity.advance();
+        assertEquals(EntityStates.T_END_OF_STREAM, entity.getState());
+    }
+
+}
Index: src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java
===================================================================
--- src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java	(revision 674082)
+++ src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java	(working copy)
@@ -39,7 +39,7 @@
             parser.next();
             fail("Expected exception to be thrown");
         } catch (MimeParseEventException e) {
-            assertEquals("Premature end of headers", MimeTokenStream.Event.HEADERS_PREMATURE_END, e.getEvent());
+            assertEquals("Premature end of headers", Event.HEADERS_PREMATURE_END, e.getEvent());
         }
      }
     
Index: src/main/java/org/apache/james/mime4j/EntityStateMachine.java
===================================================================
--- src/main/java/org/apache/james/mime4j/EntityStateMachine.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/EntityStateMachine.java	(revision 0)
@@ -0,0 +1,120 @@
+/****************************************************************
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents the interal state of a MIME entity, which is being retrieved 
+ * from an input stream by a MIME parser.
+ */
+public interface EntityStateMachine {
+
+    /**
+     * Return the current state of the entity.
+     * 
+     * @see EntityStates
+     * 
+     * @return current state
+     */
+    int getState();
+    
+    /**
+     * Sets the current recursion mode.
+     * The recursion mode specifies the approach taken to parsing parts.
+     * {@link RecursionMode#M_RAW} mode does not parse the part at all.
+     * {@link RecursionMode#M_RECURSE} mode recursively parses each mail
+     * when an <code>message/rfc822</code> part is encounted;
+     * {@link RecursionMode#M_NO_RECURSE} does not.
+     * 
+     * @see RecursionMode
+     * 
+     * @param recursionMode
+     */
+    void setRecursionMode(int recursionMode);
+    
+    /**
+     * Advances the state machine to the next state in the 
+     * process of the MIME stream parsing. This method 
+     * may return an new state machine that represents an embedded 
+     * entity, which must be parsed before the parsing process of 
+     * the current entity can proceed.
+     * 
+     * @return a state machine of an embedded entity, if encountered, 
+     * <code>null</code> otherwise.
+     *  
+     * @throws IOException if an I/O error occurs.
+     * @throws MimeException if the message can not be processed due 
+     *  to the MIME specification violation.
+     */
+    EntityStateMachine advance() throws IOException, MimeException;
+    
+    /**
+     * Returns description of the entity body.
+     * 
+     * @return body description
+     * 
+     * @throws IllegalStateException if the body description cannot be
+     *  obtained at the current stage of the parsing process. 
+     */
+    BodyDescriptor getBodyDescriptor() throws IllegalStateException;
+    
+    /**
+     * Returns content stream of the entity body.
+     * 
+     * @return input stream
+     * 
+     * @throws IllegalStateException if the content stream cannot be
+     *  obtained at the current stage of the parsing process. 
+     */
+    InputStream getContentStream() throws IllegalStateException;
+ 
+    /**
+     * Returns current header field.
+     * 
+     * @return header field
+     * 
+     * @throws IllegalStateException if a header field cannot be
+     *  obtained at the current stage of the parsing process. 
+     */
+    String getField() throws IllegalStateException;
+    
+    /**
+     * Returns name of the current header field.
+     * 
+     * @return field name
+     * 
+     * @throws IllegalStateException if a header field cannot be
+     *  obtained at the current stage of the parsing process. 
+     */
+    String getFieldName() throws IllegalStateException;
+
+    /**
+     * Returns value of the current header field.
+     * 
+     * @return field value
+     * 
+     * @throws IllegalStateException if a header field cannot be
+     *  obtained at the current stage of the parsing process. 
+     */
+    String getFieldValue() throws IllegalStateException;
+    
+}
Index: src/main/java/org/apache/james/mime4j/MimeParseEventException.java
===================================================================
--- src/main/java/org/apache/james/mime4j/MimeParseEventException.java	(revision 674082)
+++ src/main/java/org/apache/james/mime4j/MimeParseEventException.java	(working copy)
@@ -27,13 +27,13 @@
 public class MimeParseEventException extends MimeException {
     
     private static final long serialVersionUID = 4632991604246852302L;
-    private final MimeTokenStream.Event event;
+    private final Event event;
     
     /**
      * Constructs an exception 
      * @param event <code>MimeTokenStream.Event</code>, not null
      */
-    public MimeParseEventException(final MimeTokenStream.Event event) {
+    public MimeParseEventException(final Event event) {
         super(event.toString());
         this.event = event;
     }
@@ -42,7 +42,7 @@
      * Gets the causal parse event.
      * @return <code>MimeTokenStream.Event</code>, not null
      */
-    public MimeTokenStream.Event getEvent() {
+    public Event getEvent() {
         return event;
     }
 }
Index: src/main/java/org/apache/james/mime4j/Cursor.java
===================================================================
--- src/main/java/org/apache/james/mime4j/Cursor.java	(revision 674082)
+++ src/main/java/org/apache/james/mime4j/Cursor.java	(working copy)
@@ -1,122 +0,0 @@
-/****************************************************************
- * 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;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Manages parser input.
- */
-public interface Cursor {
-    
-    /**
-     * Stops processing.
-     */
-    public void stop();
-
-    /**
-     * Gets the number of CR LF sequences passed.
-     * @return number of lines passed
-     * @throws IOException
-     */
-    public int getLineNumber() throws IOException;
-
-    /**
-     * Decodes the next MIME part as BASE 64 
-     * and returns a cursor for the contents.
-     * @return <code>Cursor</code>, not null
-     * @throws IOException TODO: subclass IOException with state exception
-     */
-    public Cursor decodeBase64() throws IOException;
-
-    /**
-     * Decodes the next MIME part as Quoted Printable
-     * and returns a cursor for the contents.
-     * @return <code>Cursor</code>, not null
-     * @throws IOException TODO: subclass IOException with state exception
-     */
-    public Cursor decodeQuotedPrintable() throws IOException;
-
-    /**
-     * Advances the cursor to the next boundary.
-     * @throws IOException TODO: subclass IOException with state exception
-     */
-    public void advanceToBoundary() throws IOException;
-
-    /**
-     * Is this cursor at the end of the input?
-     * @return true if the input is at the end,
-     * false otherwise
-     * @throws IOException
-     */
-    public boolean isEnded() throws IOException;
-
-    /**
-     * Are more parts available?
-     * @return true if this cursor is reading a MIME message and 
-     * there are more parts available,
-     * false otherwise
-     * @throws IOException TODO: subclass IOException with state exception
-     */
-    public boolean moreMimeParts() throws IOException;
-
-    /**
-     * Sets the MIME boundary.
-     * 
-     * @param boundary TODO: should this be a byte sequence?
-     * @throws IOException
-     */
-    public void boundary(String boundary) throws IOException;
-
-    /**
-     * Gets a cursor for the next mime part.
-     * @return <code>Cursor</code>, not null
-     * @throws IOException TODO: subclass IOException with state exception
-     */
-    public Cursor nextMimePartCursor() throws IOException;
-
-    /**
-     * Advances the cursor.
-     * @return the next byte
-     * @throws IOException
-     * @see #nextSection()
-     */
-    public byte advance() throws IOException;
-    
-    /**
-     * Gets a stream to read the contents of the next section of the message.
-     * @return <code>InputStream</code>, not null
-     */
-    public InputStream nextSection();
-
-    /**
-     * Gets a stream to read the contents of the rest of this message.
-     * @return <code>InputStream</code>, not null
-     */
-    public InputStream rest();
-
-    /**
-     * Gets root stream for this message.
-     * @return <code>InputStream</code>, not null
-     */
-    public InputStream root();
-
-}
Index: src/main/java/org/apache/james/mime4j/RecursionMode.java
===================================================================
--- src/main/java/org/apache/james/mime4j/RecursionMode.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/RecursionMode.java	(revision 0)
@@ -0,0 +1,43 @@
+/****************************************************************
+ * 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;
+
+/**
+ * Enumeration of parsing modes.
+ */
+public interface RecursionMode {
+
+    /** 
+     * Recursively parse every <code>message/rfc822</code> part 
+     * @see #getRecursionMode() 
+     */
+    public static final int M_RECURSE = 0;
+    /**
+     * Do not recurse <code>message/rfc822</code> parts 
+     * @see #getRecursionMode()
+     */
+    public static final int M_NO_RECURSE = 1;
+    /** 
+     * Parse into raw entities
+     * @see #getRecursionMode() 
+     */
+    public static final int M_RAW = 2;
+    
+}
Index: src/main/java/org/apache/james/mime4j/Event.java
===================================================================
--- src/main/java/org/apache/james/mime4j/Event.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/Event.java	(revision 0)
@@ -0,0 +1,46 @@
+package org.apache.james.mime4j;
+
+/**
+ * Enumerates events which can be monitored.
+ */
+public final class Event { 
+
+    /** Indicates that a body part ended prematurely. */
+    public static final Event MIME_BODY_PREMATURE_END 
+        = new Event("Body part ended prematurely. " +
+                "Boundary detected in header or EOF reached."); 
+    /** Indicates that unexpected end of headers detected.*/
+    public static final Event HEADERS_PREMATURE_END 
+        = new Event("Unexpected end of headers detected. " +
+                "Higher level boundary detected or EOF reached.");
+    
+    private final String code;
+    
+    public Event(final String code) {
+        super();
+        if (code == null) {
+            throw new IllegalArgumentException("Code may not be null");
+        }
+        this.code = code;
+    }
+    
+    public int hashCode() {
+        return code.hashCode();
+    }
+
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+        if (this == obj) return true;
+        if (obj instanceof Event) {
+            Event that = (Event) obj;
+            return this.code.equals(that.code);
+        } else {
+            return false;
+        }
+    }
+    
+    public String toString() {
+        return code;
+    }
+    
+}
\ No newline at end of file
Index: src/main/java/org/apache/james/mime4j/MimeEntity.java
===================================================================
--- src/main/java/org/apache/james/mime4j/MimeEntity.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/MimeEntity.java	(revision 0)
@@ -0,0 +1,241 @@
+package org.apache.james.mime4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.james.mime4j.decoder.Base64InputStream;
+import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
+import org.apache.james.mime4j.util.MimeUtil;
+
+public class MimeEntity extends AbstractEntity {
+
+    /**
+     * Internal state, not exposed.
+     */
+    private static final int T_IN_BODYPART = -2;
+    /**
+     * Internal state, not exposed.
+     */
+    private static final int T_IN_MESSAGE = -3;
+
+    private final InputBuffer inbuffer;
+    private final InputStream rawStream;
+    
+    private int recursionMode;
+    private MimeBoundaryInputStream mimeStream;
+    private EOFSensitiveInputStream dataStream;
+    private boolean skipHeader;
+    
+    public MimeEntity(
+            RootInputStream rootStream,
+            InputStream rawStream,
+            InputBuffer inbuffer,
+            BodyDescriptor parent, 
+            int startState, 
+            int endState,
+            boolean maximalBodyDescriptor,
+            boolean strictParsing) {
+        super(rootStream, parent, startState, endState, maximalBodyDescriptor, strictParsing);
+        this.inbuffer = inbuffer;
+        this.rawStream = rawStream;
+        this.dataStream = new EOFSensitiveInputStream(rawStream);
+        this.skipHeader = false;
+    }
+
+    public MimeEntity(
+            RootInputStream rootStream,
+            InputStream rawStream,
+            InputBuffer inbuffer,
+            BodyDescriptor parent, 
+            int startState, 
+            int endState) {
+        this(rootStream, rawStream, inbuffer, parent, startState, endState, false, false);
+    }
+
+    public int getRecursionMode() {
+        return recursionMode;
+    }
+
+    public void setRecursionMode(int recursionMode) {
+        this.recursionMode = recursionMode;
+    }
+
+    public void skipHeader(String contentType) {
+        if (state != EntityStates.T_START_MESSAGE) {
+            throw new IllegalStateException("Invalid state: " + stateToString(state));
+        }
+        skipHeader = true;
+        body.addField("Content-Type", contentType);
+    }
+    
+    protected InputStream getDataStream() {
+        return dataStream;
+    }
+    
+    public EntityStateMachine advance() throws IOException, MimeException {
+        switch (state) {
+        case EntityStates.T_START_MESSAGE:
+            if (skipHeader) {
+                state = EntityStates.T_END_HEADER;
+            } else {
+                state = EntityStates.T_START_HEADER;
+            }
+            break;
+        case EntityStates.T_START_BODYPART:
+            state = EntityStates.T_START_HEADER;
+            break;
+        case EntityStates.T_START_HEADER:
+            initHeaderParsing();
+            state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
+            break;
+        case EntityStates.T_FIELD:
+            state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
+            break;
+        case EntityStates.T_END_HEADER:
+            String mimeType = body.getMimeType();
+            if (MimeUtil.isMultipart(mimeType)) {
+                state = EntityStates.T_START_MULTIPART;
+                clearMimeStream();
+            } else if (recursionMode != RecursionMode.M_NO_RECURSE 
+                    && MimeUtil.isMessage(mimeType)) {
+                state = T_IN_MESSAGE;
+                return nextMessage();
+            } else {
+                state = EntityStates.T_BODY;
+            }
+            break;
+        case EntityStates.T_START_MULTIPART:
+            if (dataStream.isUsed()) {
+                advanceToBoundary();            
+                state = EntityStates.T_END_MULTIPART;
+            } else {
+                createMimeStream();
+                state = EntityStates.T_PREAMBLE;
+            }
+            break;
+        case EntityStates.T_PREAMBLE:
+            advanceToBoundary();            
+            if (mimeStream.isLastPart()) {
+                clearMimeStream();
+                state = EntityStates.T_END_MULTIPART;
+            } else {
+                createMimeStream();
+                state = T_IN_BODYPART;
+                return nextMimeEntity();
+            }
+            break;
+        case T_IN_BODYPART:
+            advanceToBoundary();
+            if (mimeStream.eof() && !mimeStream.isLastPart()) {
+                monitor(Event.MIME_BODY_PREMATURE_END);
+            } else {
+                if (!mimeStream.isLastPart()) {
+                    createMimeStream();
+                    state = T_IN_BODYPART;
+                    return nextMimeEntity();
+                }
+            }
+            clearMimeStream();
+            state = EntityStates.T_EPILOGUE;
+            break;
+        case EntityStates.T_EPILOGUE:
+            state = EntityStates.T_END_MULTIPART;
+            break;
+        case EntityStates.T_BODY:
+        case EntityStates.T_END_MULTIPART:
+        case T_IN_MESSAGE:
+            state = endState;
+            break;
+        default:
+            if (state == endState) {
+                state = EntityStates.T_END_OF_STREAM;
+                break;
+            }
+            throw new IllegalStateException("Invalid state: " + stateToString(state));
+        }
+        return null;
+    }
+
+    private void createMimeStream() throws IOException {
+        mimeStream = new MimeBoundaryInputStream(inbuffer, body.getBoundary());
+        dataStream = new EOFSensitiveInputStream(mimeStream); 
+    }
+    
+    private void clearMimeStream() {
+        mimeStream = null;
+        dataStream = new EOFSensitiveInputStream(rawStream); 
+    }
+    
+    private void advanceToBoundary() throws IOException {
+        if (!dataStream.eof()) {
+            byte[] tmp = new byte[2048];
+            while (dataStream.read(tmp)!= -1) {
+            }
+        }
+    }
+    
+    private EntityStateMachine nextMessage() {
+        String transferEncoding = body.getTransferEncoding();
+        InputStream instream;
+        if (MimeUtil.isBase64Encoding(transferEncoding)) {
+            log.debug("base64 encoded message/rfc822 detected");
+            instream = new EOLConvertingInputStream(
+                    new Base64InputStream(mimeStream));                    
+        } else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) {
+            log.debug("quoted-printable encoded message/rfc822 detected");
+            instream = new EOLConvertingInputStream(
+                    new QuotedPrintableInputStream(mimeStream));                    
+        } else {
+            instream = dataStream;
+        }
+        
+        if (recursionMode == RecursionMode.M_RAW) {
+            RawEntity message = new RawEntity(instream);
+            return message;
+        } else {
+            MimeEntity message = new MimeEntity(
+                    rootStream, 
+                    instream,
+                    inbuffer, 
+                    body, 
+                    EntityStates.T_START_MESSAGE, 
+                    EntityStates.T_END_MESSAGE,
+                    maximalBodyDescriptor,
+                    strictParsing);
+            message.setRecursionMode(recursionMode);
+            return message;
+        }
+    }
+    
+    private EntityStateMachine nextMimeEntity() {
+        if (recursionMode == RecursionMode.M_RAW) {
+            RawEntity message = new RawEntity(mimeStream);
+            return message;
+        } else {
+            MimeEntity mimeentity = new MimeEntity(
+                    rootStream, 
+                    mimeStream,
+                    inbuffer, 
+                    body, 
+                    EntityStates.T_START_BODYPART, 
+                    EntityStates.T_END_BODYPART,
+                    maximalBodyDescriptor,
+                    strictParsing);
+            mimeentity.setRecursionMode(recursionMode);
+            return mimeentity;
+        }
+    }
+    
+    public InputStream getContentStream() {
+        switch (state) {
+        case EntityStates.T_START_MULTIPART:
+        case EntityStates.T_PREAMBLE:
+        case EntityStates.T_EPILOGUE:
+        case EntityStates.T_BODY:
+            return this.dataStream;
+        default:
+            throw new IllegalStateException("Invalid state: " + stateToString(state));
+        }
+    }
+
+}
Index: src/main/java/org/apache/james/mime4j/RawEntity.java
===================================================================
--- src/main/java/org/apache/james/mime4j/RawEntity.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/RawEntity.java	(revision 0)
@@ -0,0 +1,90 @@
+/****************************************************************
+ * 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;
+
+import java.io.InputStream;
+
+/**
+ * Raw MIME entity. Such entities will not be parsed into elements 
+ * by the parser. They are meant to be consumed as a raw data stream
+ * by the caller.  
+ */
+public class RawEntity implements EntityStateMachine {
+
+    private final InputStream stream;
+
+    private int state;
+    
+    RawEntity(InputStream stream) {
+        this.stream = stream;
+        this.state = EntityStates.T_RAW_ENTITY;
+    }
+    
+    public int getState() {
+        return state;
+    }
+
+    /**
+     * This method has no effect.
+     */
+    public void setRecursionMode(int recursionMode) {
+    }
+
+    public EntityStateMachine advance() {
+        state = EntityStates.T_END_OF_STREAM;
+        return null;
+    }
+    
+    /**
+     * Returns raw data stream.
+     */
+    public InputStream getContentStream() {
+        return stream;
+    }
+
+    /**
+     * This method has no effect and always returns <code>null</code>.
+     */
+    public BodyDescriptor getBodyDescriptor() {
+        return null;
+    }
+
+    /**
+     * This method has no effect and always returns <code>null</code>.
+     */
+    public String getField() {
+        return null;
+    }
+
+    /**
+     * This method has no effect and always returns <code>null</code>.
+     */
+    public String getFieldName() {
+        return null;
+    }
+
+    /**
+     * This method has no effect and always returns <code>null</code>.
+     */
+    public String getFieldValue() {
+        return null;
+    }
+    
+}
Index: src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java
===================================================================
--- src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java	(revision 0)
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <code>InputStream</code> used by the MIME parser to detect whether the
+ * underlying data stream was used (read from) and whether the end of the 
+ * stream was reached.
+ * 
+ * @version $Id$
+ */
+class EOFSensitiveInputStream extends FilterInputStream {
+
+    private boolean used = false;
+    private boolean eof = false;
+
+    public EOFSensitiveInputStream(InputStream is) {
+        super(is);
+    }
+
+    public int read() throws IOException {
+        int i = super.read();
+        this.eof = i == -1;
+        this.used = true;
+        return i;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        int i = super.read(b, off, len);
+        this.eof = i == -1;
+        this.used = true;
+        return i;
+    }
+    
+    public boolean eof() {
+        return this.eof;
+    }
+
+    public boolean isUsed() {
+        return this.used;
+    }
+
+}
Index: src/main/java/org/apache/james/mime4j/EntityStates.java
===================================================================
--- src/main/java/org/apache/james/mime4j/EntityStates.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/EntityStates.java	(revision 0)
@@ -0,0 +1,101 @@
+/****************************************************************
+ * 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;
+
+/**
+ * Enumeration of states an entity is expected to go through
+ * in the process of the MIME stream parsing.
+ */
+public interface EntityStates {
+
+    /**
+     * This token indicates, that the MIME stream has been completely
+     * and successfully parsed, and no more data is available.
+     */
+    public static final int T_END_OF_STREAM = -1;
+    /**
+     * This token indicates, that the MIME stream is currently
+     * at the beginning of a message.
+     */
+    public static final int T_START_MESSAGE = 0;
+    /**
+     * This token indicates, that the MIME stream is currently
+     * at the end of a message.
+     */
+    public static final int T_END_MESSAGE = 1;
+    /**
+     * This token indicates, that a raw entity is currently being processed.
+     * You may call {@link #getContentStream()} to obtain the raw entity
+     * data.
+     */
+    public static final int T_RAW_ENTITY = 2;
+    /**
+     * This token indicates, that a message parts headers are now
+     * being parsed.
+     */
+    public static final int T_START_HEADER = 3;
+    /**
+     * This token indicates, that a message parts field has now
+     * been parsed. You may call {@link #getField()} to obtain the
+     * raw field contents.
+     */
+    public static final int T_FIELD = 4;
+    /**
+     * This token indicates, that part headers have now been
+     * parsed.
+     */
+    public static final int T_END_HEADER = 5;
+    /**
+     * This token indicates, that a multipart body is being parsed.
+     */
+    public static final int T_START_MULTIPART = 6;
+    /**
+     * This token indicates, that a multipart body has been parsed.
+     */
+    public static final int T_END_MULTIPART = 7;
+    /**
+     * This token indicates, that a multiparts preamble is being
+     * parsed. You may call {@link #getContentStream()} to access the
+     * preamble contents.
+     */
+    public static final int T_PREAMBLE = 8;
+    /**
+     * This token indicates, that a multiparts epilogue is being
+     * parsed. You may call {@link #getContentStream()} to access the
+     * epilogue contents.
+     */
+    public static final int T_EPILOGUE = 9;
+    /**
+     * This token indicates, that the MIME stream is currently
+     * at the beginning of a body part.
+     */
+    public static final int T_START_BODYPART = 10;
+    /**
+     * This token indicates, that the MIME stream is currently
+     * at the end of a body part.
+     */
+    public static final int T_END_BODYPART = 11;
+    /**
+     * This token indicates, that an atomic entity is being parsed.
+     * Use {@link #getContentStream()} to access the entity contents.
+     */
+    public static final int T_BODY = 12;
+
+}
Index: src/main/java/org/apache/james/mime4j/AbstractEntity.java
===================================================================
--- src/main/java/org/apache/james/mime4j/AbstractEntity.java	(revision 0)
+++ src/main/java/org/apache/james/mime4j/AbstractEntity.java	(revision 0)
@@ -0,0 +1,381 @@
+/****************************************************************
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.BitSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Abstract MIME entity.
+ */
+public abstract class AbstractEntity implements EntityStateMachine {
+
+    protected final Log log;
+    
+    protected final RootInputStream rootStream;
+    protected final BodyDescriptor parent;
+    protected final int startState;
+    protected final int endState;
+    protected final boolean maximalBodyDescriptor;
+    protected final boolean strictParsing;
+    protected final MutableBodyDescriptor body;
+    
+    protected int state;
+
+    private final StringBuffer sb = new StringBuffer();
+    
+    private int pos, start;
+    private int lineNumber, startLineNumber;
+    
+    private String field, fieldName, fieldValue;
+
+    private static final BitSet fieldChars = new BitSet();
+
+    static {
+        for (int i = 0x21; i <= 0x39; i++) {
+            fieldChars.set(i);
+        }
+        for (int i = 0x3b; i <= 0x7e; i++) {
+            fieldChars.set(i);
+        }
+    }
+
+    /**
+     * Internal state, not exposed.
+     */
+    private static final int T_IN_BODYPART = -2;
+    /**
+     * Internal state, not exposed.
+     */
+    private static final int T_IN_MESSAGE = -3;
+
+    AbstractEntity(
+            RootInputStream rootStream,
+            BodyDescriptor parent,
+            int startState, 
+            int endState,
+            boolean maximalBodyDescriptor,
+            boolean strictParsing) {
+        this.log = LogFactory.getLog(getClass());        
+        this.rootStream = rootStream;
+        this.parent = parent;
+        this.state = startState;
+        this.startState = startState; 
+        this.endState = endState;
+        this.maximalBodyDescriptor = maximalBodyDescriptor;
+        this.strictParsing = strictParsing;
+        this.body = newBodyDescriptor(parent);
+    }
+
+    public int getState() {
+        return state;
+    }
+    
+    /**
+     * Creates a new instance of {@link BodyDescriptor}. Subclasses may override
+     * this in order to create body descriptors, that provide more specific
+     * information.
+     */
+    protected MutableBodyDescriptor newBodyDescriptor(BodyDescriptor pParent) {
+        final MutableBodyDescriptor result;
+        if (maximalBodyDescriptor) {
+            result = new MaximalBodyDescriptor(pParent);
+        } else {
+            result = new DefaultBodyDescriptor(pParent);
+        }
+        return result;
+    }
+    
+    protected abstract InputStream getDataStream();
+    
+    protected void initHeaderParsing() throws IOException, MimeException {
+        startLineNumber = lineNumber = rootStream.getLineNumber();
+
+        InputStream instream = getDataStream();
+        
+        int curr = 0;
+        int prev = 0;
+        while ((curr = instream.read()) != -1) {
+            if (curr == '\n' && (prev == '\n' || prev == 0)) {
+                /*
+                 * [\r]\n[\r]\n or an immediate \r\n have been seen.
+                 */
+                sb.deleteCharAt(sb.length() - 1);
+                break;
+            }
+            sb.append((char) curr);
+            prev = curr == '\r' ? prev : curr;
+        }
+        
+        if (curr == -1) {
+            monitor(Event.HEADERS_PREMATURE_END);
+        }
+    }
+
+    protected boolean parseField() {
+        while (pos < sb.length()) {
+            while (pos < sb.length() && sb.charAt(pos) != '\r') {
+                pos++;
+            }
+            if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') {
+                pos++;
+                continue;
+            }
+            if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) {
+                /*
+                 * field should be the complete field data excluding the 
+                 * trailing \r\n.
+                 */
+                field = sb.substring(start, pos);
+                start = pos + 2;
+                
+                /*
+                 * Check for a valid field.
+                 */
+                int index = field.indexOf(':');
+                boolean valid = false;
+                if (index != -1 && fieldChars.get(field.charAt(0))) {
+                    valid = true;
+                    fieldName = field.substring(0, index).trim();
+                    for (int i = 0; i < fieldName.length(); i++) {
+                        if (!fieldChars.get(fieldName.charAt(i))) {
+                            valid = false;
+                            break;
+                        }
+                    }
+                    if (valid) {
+                        fieldValue = field.substring(index + 1);
+                        body.addField(fieldName, fieldValue);
+                        startLineNumber = lineNumber;
+                        pos += 2;
+                        lineNumber++;
+                        return true;
+                    }
+                }
+                if (log.isWarnEnabled()) {
+                    log.warn("Line " + startLineNumber 
+                            + ": Ignoring invalid field: '" + field.trim() + "'");
+                }
+                startLineNumber = lineNumber;
+            }
+            pos += 2;
+            lineNumber++;
+        }
+        return false;
+    }
+
+    /**
+     * <p>Gets a descriptor for the current entity.
+     * This method is valid if {@link #getState()} returns:</p>
+     * <ul>
+     * <li>{@link #T_BODY}</li>
+     * <li>{@link #T_START_MULTIPART}</li>
+     * <li>{@link #T_EPILOGUE}</li>
+     * <li>{@link #T_PREAMBLE}</li>
+     * </ul>
+     * @return <code>BodyDescriptor</code>, not nulls
+     */
+    public BodyDescriptor getBodyDescriptor() {
+        switch (getState()) {
+        case EntityStates.T_BODY:
+        case EntityStates.T_START_MULTIPART:
+        case EntityStates.T_PREAMBLE:
+        case EntityStates.T_EPILOGUE:
+            return body;
+        default:
+            throw new IllegalStateException("Invalid state :" + stateToString(state));
+        }
+    }
+
+    /**
+     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
+     * @return String with the fields raw contents.
+     * @throws IllegalStateException {@link #getState()} returns another
+     *   value than {@link #T_FIELD}.
+     */
+    public String getField() {
+        switch (getState()) {
+        case EntityStates.T_FIELD:
+            return field;
+        default:
+            throw new IllegalStateException("Invalid state :" + stateToString(state));
+        }
+    }
+
+    /**
+     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
+     * @return String with the fields name.
+     * @throws IllegalStateException {@link #getState()} returns another
+     *   value than {@link #T_FIELD}.
+     */
+    public String getFieldName() {
+        switch (getState()) {
+        case EntityStates.T_FIELD:
+            return fieldName;
+        default:
+            throw new IllegalStateException("Invalid state :" + stateToString(state));
+        }
+    }
+
+    /**
+     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
+     * @return String with the fields value.
+     * @throws IllegalStateException {@link #getState()} returns another
+     *   value than {@link #T_FIELD}.
+     */
+    public String getFieldValue() {
+        switch (getState()) {
+        case EntityStates.T_FIELD:
+            return fieldValue;
+        default:
+            throw new IllegalStateException("Invalid state :" + stateToString(state));
+        }
+    }
+
+    /**
+     * Monitors the given event.
+     * Subclasses may override to perform actions upon events.
+     * Base implementation logs at warn.
+     * @param event <code>Event</code>, not null
+     * @throws MimeException subclasses may elect to throw this exception upon
+     * invalid content
+     * @throws IOException subclasses may elect to throw this exception
+     */
+    protected void monitor(Event event) throws MimeException, IOException {
+        if (strictParsing) {
+            throw new MimeParseEventException(event);
+        } else {
+            warn(event);
+        }
+    }
+    
+    /**
+     * Creates an indicative message suitable for display
+     * based on the given event and the current state of the system.
+     * @param event <code>Event</code>, not null
+     * @return message suitable for use as a message in an exception
+     * or for logging
+     */
+    protected String message(Event event) {
+        String preamble = "Line " + rootStream.getLineNumber() + ": ";
+        final String message;
+        if (event == null) {
+            message = "Event is unexpectedly null.";
+        } else {
+            message = event.toString();
+        }
+        final String result = preamble + message;
+        return result;
+    }
+    
+    /**
+     * Logs (at warn) an indicative message based on the given event 
+     * and the current state of the system.
+     * @param event <code>Event</code>, not null
+     */
+    protected void warn(Event event) {
+        if (log.isWarnEnabled()) {
+            log.warn(message(event));
+        }
+    }
+    
+    /**
+     * Logs (at debug) an indicative message based on the given event
+     * and the current state of the system.
+     * @param event <code>Event</code>, not null
+     */
+    protected void debug(Event event) {
+        if (log.isDebugEnabled()) {
+            log.debug(message(event));
+        }
+    }
+
+    public String toString() {
+        return "Current state: " + stateToString(state);
+    }
+
+    /**
+     * Renders a state as a string suitable for logging.
+     * @param state 
+     * @return rendered as string, not null
+     */
+    public static final String stateToString(int state) {
+        final String result;
+        switch (state) {
+            case EntityStates.T_END_OF_STREAM:
+                result = "End of stream";
+                break;
+            case EntityStates.T_START_MESSAGE:
+                result = "Start message";
+                break;
+            case EntityStates.T_END_MESSAGE:
+                result = "End message";
+                break;
+            case EntityStates.T_RAW_ENTITY:
+                result = "Raw entity";
+                break;
+            case EntityStates.T_START_HEADER:
+                result = "Start header";
+                break;
+            case EntityStates.T_FIELD:
+                result = "Field";
+                break;
+            case EntityStates.T_END_HEADER:
+                result = "End header";
+                break;
+            case EntityStates.T_START_MULTIPART:
+                result = "Start multipart";
+                break;
+            case EntityStates.T_END_MULTIPART:
+                result = "End multipart";
+                break;
+            case EntityStates.T_PREAMBLE:
+                result = "Premable";
+                break;
+            case EntityStates.T_EPILOGUE:
+                result = "Epilogue";
+                break;
+            case EntityStates.T_START_BODYPART:
+                result = "Start bodypart";
+                break;
+            case EntityStates.T_END_BODYPART:
+                result = "End bodypart";
+                break;
+            case EntityStates.T_BODY:
+                result = "Body";
+                break;
+            case T_IN_BODYPART:
+                result = "Bodypart";
+                break;
+            case T_IN_MESSAGE:
+                result = "In message";
+                break;
+            default:
+                result = "Unknown";
+                break;
+        }
+        return result;
+    }
+    
+}
Index: src/main/java/org/apache/james/mime4j/MimeTokenStream.java
===================================================================
--- src/main/java/org/apache/james/mime4j/MimeTokenStream.java	(revision 674082)
+++ src/main/java/org/apache/james/mime4j/MimeTokenStream.java	(working copy)
@@ -24,17 +24,14 @@
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.List;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.LinkedList;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.apache.james.mime4j.decoder.Base64InputStream;
 import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
 import org.apache.james.mime4j.util.MimeUtil;
 
-
 /**
  * <p>
  * Parses MIME (or RFC822) message streams of bytes or characters.
@@ -45,7 +42,7 @@
  * </p>
  * <pre>
  *      MimeTokenStream stream = new MimeTokenStream();
- *      stream.parse(new BufferedInputStream(new FileInputStream("mime.msg")));
+ *      stream.parse(new FileInputStream("mime.msg"));
  *      for (int state = stream.getState();
  *           state != MimeTokenStream.T_END_OF_STREAM;
  *           state = stream.next()) {
@@ -80,180 +77,9 @@
  * 
  * @version $Id: MimeStreamParser.java,v 1.8 2005/02/11 10:12:02 ntherning Exp $
  */
-public class MimeTokenStream {
-    private static final Log log = LogFactory.getLog(MimeTokenStream.class);
-
-    /**
-     * This token indicates, that the MIME stream has been completely
-     * and successfully parsed, and no more data is available.
-     */
-    public static final int T_END_OF_STREAM = -1;
-    /**
-     * This token indicates, that the MIME stream is currently
-     * at the beginning of a message.
-     */
-    public static final int T_START_MESSAGE = 0;
-    /**
-     * This token indicates, that the MIME stream is currently
-     * at the end of a message.
-     */
-    public static final int T_END_MESSAGE = 1;
-    /**
-     * This token indicates, that a raw entity is currently being processed.
-     * You may call {@link #getInputStream()} to obtain the raw entity
-     * data.
-     */
-    public static final int T_RAW_ENTITY = 2;
-    /**
-     * This token indicates, that a message parts headers are now
-     * being parsed.
-     */
-    public static final int T_START_HEADER = 3;
-    /**
-     * This token indicates, that a message parts field has now
-     * been parsed. You may call {@link #getField()} to obtain the
-     * raw field contents.
-     */
-    public static final int T_FIELD = 4;
-    /**
-     * This token indicates, that part headers have now been
-     * parsed.
-     */
-    public static final int T_END_HEADER = 5;
-    /**
-     * This token indicates, that a multipart body is being parsed.
-     */
-    public static final int T_START_MULTIPART = 6;
-    /**
-     * This token indicates, that a multipart body has been parsed.
-     */
-    public static final int T_END_MULTIPART = 7;
-    /**
-     * This token indicates, that a multiparts preamble is being
-     * parsed. You may call {@link #getInputStream()} to access the
-     * preamble contents.
-     */
-    public static final int T_PREAMBLE = 8;
-    /**
-     * This token indicates, that a multiparts epilogue is being
-     * parsed. You may call {@link #getInputStream()} to access the
-     * epilogue contents.
-     */
-    public static final int T_EPILOGUE = 9;
-    /**
-     * This token indicates, that the MIME stream is currently
-     * at the beginning of a body part.
-     */
-    public static final int T_START_BODYPART = 10;
-    /**
-     * This token indicates, that the MIME stream is currently
-     * at the end of a body part.
-     */
-    public static final int T_END_BODYPART = 11;
-    /**
-     * This token indicates, that an atomic entity is being parsed.
-     * Use {@link #getInputStream()} to access the entity contents.
-     */
-    public static final int T_BODY = 12;
-    /**
-     * Internal state, not exposed.
-     */
-    private static final int T_IN_BODYPART = -2;
-    /**
-     * Internal state, not exposed.
-     */
-    private static final int T_IN_MESSAGE = -3;
-
-    private static final BitSet fieldChars = new BitSet();
-    static {
-        for (int i = 0x21; i <= 0x39; i++) {
-            fieldChars.set(i);
-        }
-        for (int i = 0x3b; i <= 0x7e; i++) {
-            fieldChars.set(i);
-        }
-    }
-
-    /** 
-     * Recursively parse every <code>message/rfc822</code> part 
-     * @see #getRecursionMode() 
-     */
-    public static final int M_RECURSE = 0;
-    /**
-     * Do not recurse <code>message/rfc822</code> parts 
-     * @see #getRecursionMode()
-     */
-    public static final int M_NO_RECURSE = 1;
-    /** 
-     * Parse into raw entities
-     * @see #getRecursionMode() 
-     */
-    public static final int M_RAW = 2;
+public class MimeTokenStream implements EntityStates, RecursionMode {
     
     /**
-     * Renders a state as a string suitable for logging.
-     * @param state 
-     * @return rendered as string, not null
-     */
-    public static final String stateToString(int state) {
-        final String result;
-        switch (state) {
-            case T_END_OF_STREAM:
-                result = "End of stream";
-                break;
-            case T_START_MESSAGE:
-                result = "Start message";
-                break;
-            case T_END_MESSAGE:
-                result = "End message";
-                break;
-            case T_RAW_ENTITY:
-                result = "Raw entity";
-                break;
-            case T_START_HEADER:
-                result = "Start header";
-                break;
-            case T_FIELD:
-                result = "Field";
-                break;
-            case T_END_HEADER:
-                result = "End header";
-                break;
-            case T_START_MULTIPART:
-                result = "Start multipart";
-                break;
-            case T_END_MULTIPART:
-                result = "End multipart";
-                break;
-            case T_PREAMBLE:
-                result = "Premable";
-                break;
-            case T_EPILOGUE:
-                result = "Epilogue";
-                break;
-            case T_START_BODYPART:
-                result = "Start bodypart";
-                break;
-            case T_END_BODYPART:
-                result = "End bodypart";
-                break;
-            case T_BODY:
-                result = "Body";
-                break;
-            case T_IN_BODYPART:
-                result = "Bodypart";
-                break;
-            case T_IN_MESSAGE:
-                result = "In message";
-                break;
-            default:
-                result = "Unknown";
-                break;
-        }
-        return result;
-    }
-    
-    /**
      * Creates a stream that creates a more detailed body descriptor.
      * @return <code>MimeTokenStream</code>, not null
      */
@@ -271,298 +97,15 @@
         return new MimeTokenStream(true, false);
     }
     
-    /**
-     * Enumerates events which can be monitored.
-     */
-    public final static class Event { 
-
-        /** Indicates that a body part ended prematurely. */
-        public static final Event MIME_BODY_PREMATURE_END 
-            = new Event("Body part ended prematurely. " +
-                    "Boundary detected in header or EOF reached."); 
-        /** Indicates that unexpected end of headers detected.*/
-        public static final Event HEADERS_PREMATURE_END 
-            = new Event("Unexpected end of headers detected. " +
-                    "Higher level boundary detected or EOF reached.");
-        
-        private final String code;
-        
-        private Event(final String code) {
-            super();
-            this.code = code;
-        }
-        
-        public int hashCode() {
-            return code.hashCode();
-        }
-
-        public boolean equals(Object obj) {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            final Event other = (Event) obj;
-            return code.equals(other.code);
-        }
-        
-        public String toString() {
-            return code;
-        }
-    }
-
-    abstract static class StateMachine {
-        int state;
-        abstract int next() throws IOException, MimeException;
-        abstract InputStream read();
-    }
-
-    private static class RawEntity extends StateMachine {
-        private InputStream stream;
-        RawEntity(InputStream stream) {
-            this.stream = stream;
-            state = T_RAW_ENTITY;
-        }
-        int next() {
-            state = T_END_OF_STREAM;
-            return state;
-        }
-        InputStream read() {
-            return stream;
-        }
-    }
-
-    private abstract class Entity extends StateMachine {
-        private final BodyDescriptor parent;
-        private final Cursor cursor;
-        private final StringBuffer sb = new StringBuffer();
-        private MutableBodyDescriptor body;
-        private int pos, start;
-        private int lineNumber, startLineNumber;
-        private final int endState;
-        private boolean raw;
-        
-        String field, fieldName, fieldValue;
-
-        Entity(Cursor cursor, BodyDescriptor parent, int startState, int endState) {
-            this.parent = parent;
-            this.cursor = cursor;
-            state = startState;
-            this.endState = endState;
-        }
-
-        private void setParsingFieldState() {
-            state = parseField() ? T_FIELD : T_END_HEADER;
-        }
-
-        private int setParseBodyPartState() throws IOException, MimeException {
-            cursor.advanceToBoundary();
-            if (cursor.isEnded() && cursor.moreMimeParts()) {
-                monitor(Event.MIME_BODY_PREMATURE_END);
-            } else {
-                if (cursor.moreMimeParts()) {
-                    final String boundary = body.getBoundary();
-                    cursor.boundary(boundary);
-                   
-                    if (isRaw()) {
-                        currentStateMachine = new RawEntity(cursor.nextSection());
-                    } else {
-                        currentStateMachine = new BodyPart(cursor.nextMimePartCursor(), body);
-                    }
-                    entities.add(currentStateMachine);
-                    state = T_IN_BODYPART;
-                    return currentStateMachine.state;
-                }
-            }
-            state = T_EPILOGUE;
-            return T_EPILOGUE;
-        }
-
-        int next() throws IOException, MimeException {
-            switch (state) {
-                case T_START_MESSAGE:
-                case T_START_BODYPART:
-                    state = T_START_HEADER;
-                    break;
-                case T_START_HEADER:
-                    initHeaderParsing();
-                    setParsingFieldState();
-                    break;
-                case T_FIELD:
-                    setParsingFieldState();
-                    break;
-                case T_END_HEADER:
-                    final String mimeType = body.getMimeType();
-                    if (MimeUtil.isMultipart(mimeType)) {
-                        state = T_START_MULTIPART;
-                    } else if (recursionMode != M_NO_RECURSE && MimeUtil.isMessage(mimeType)) {
-                        Cursor nextCursor = cursor;
-                        final String transferEncoding = body.getTransferEncoding();
-                        if (MimeUtil.isBase64Encoding(transferEncoding)) {
-                            log.debug("base64 encoded message/rfc822 detected");
-                            nextCursor = cursor.decodeBase64();
-                        } else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) {
-                            log.debug("quoted-printable encoded message/rfc822 detected");
-                            nextCursor = cursor.decodeQuotedPrintable();
-                        }
-                        state = T_IN_MESSAGE;
-                        return parseMessage(nextCursor, body);
-                    } else {
-                        state = T_BODY;
-                        break;
-                    }
-                    break;
-                case T_START_MULTIPART:
-                    cursor.boundary(body.getBoundary());
-                    if (cursor.isEnded() || raw) {
-                        state = T_END_MULTIPART;
-                    } else {
-                        state = T_PREAMBLE;
-                    }
-                    break;
-                case T_PREAMBLE:
-                    return setParseBodyPartState();
-                case T_IN_BODYPART:
-                    return setParseBodyPartState();
-                case T_EPILOGUE:
-                    state = T_END_MULTIPART;
-                    break;
-                case T_BODY:
-                case T_END_MULTIPART:
-                case T_IN_MESSAGE:
-                    state = endState;
-                    break;
-                default:
-                    if (state == endState) {
-                        state = T_END_OF_STREAM;
-                        break;
-                    }
-                    throw new IllegalStateException("Invalid state: " + state);
-            }
-            return state;
-        }
-
-        InputStream read() {
-            switch (getState()) {
-                case T_PREAMBLE:
-                case T_EPILOGUE:
-                case T_BODY:
-                    return cursor.nextSection();
-                case T_START_MULTIPART:
-                    raw = true;
-                    return cursor.rest();
-                default:
-                    throw new IllegalStateException("Expected state to be either of T_RAW_ENTITY, T_PREAMBLE, or T_EPILOGUE.");
-            }
-        }
-        
-        private void initHeaderParsing() throws IOException, MimeException {
-            body = newBodyDescriptor(parent);
-            startLineNumber = lineNumber = cursor.getLineNumber();
-
-            int curr = 0;
-            int prev = 0;
-            while ((curr = cursor.advance()) != -1) {
-                if (curr == '\n' && (prev == '\n' || prev == 0)) {
-                    /*
-                     * [\r]\n[\r]\n or an immediate \r\n have been seen.
-                     */
-                    sb.deleteCharAt(sb.length() - 1);
-                    break;
-                }
-                sb.append((char) curr);
-                prev = curr == '\r' ? prev : curr;
-            }
-            
-            if (curr == -1) {
-                monitor(Event.HEADERS_PREMATURE_END);
-            }
-        }
-
-        private boolean parseField() {
-            while (pos < sb.length()) {
-                while (pos < sb.length() && sb.charAt(pos) != '\r') {
-                    pos++;
-                }
-                if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') {
-                    pos++;
-                    continue;
-                }
-                if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) {
-                    /*
-                     * field should be the complete field data excluding the 
-                     * trailing \r\n.
-                     */
-                    field = sb.substring(start, pos);
-                    start = pos + 2;
-                    
-                    /*
-                     * Check for a valid field.
-                     */
-                    int index = field.indexOf(':');
-                    boolean valid = false;
-                    if (index != -1 && fieldChars.get(field.charAt(0))) {
-                        valid = true;
-                        fieldName = field.substring(0, index).trim();
-                        for (int i = 0; i < fieldName.length(); i++) {
-                            if (!fieldChars.get(fieldName.charAt(i))) {
-                                valid = false;
-                                break;
-                            }
-                        }
-                        if (valid) {
-                            fieldValue = field.substring(index + 1);
-                            body.addField(fieldName, fieldValue);
-                            startLineNumber = lineNumber;
-                            pos += 2;
-                            lineNumber++;
-                            return true;
-                        }
-                    }
-                    if (log.isWarnEnabled()) {
-                        log.warn("Line " + startLineNumber 
-                                + ": Ignoring invalid field: '" + field.trim() + "'");
-                    }
-                    startLineNumber = lineNumber;
-                }
-                pos += 2;
-                lineNumber++;
-            }
-            return false;
-        }
-    }
-
-    private class Message extends Entity {
-        Message(Cursor cursor, BodyDescriptor parent) {
-            super(cursor, parent, T_START_MESSAGE, T_END_MESSAGE);
-        }
-
-        InputStream read() {
-            switch (getState()) {
-            case T_EPILOGUE:
-                return cursor.root();
-            default:
-                return super.read();
-            }
-        }
-        
-    }
-
-    private class BodyPart extends Entity {
-        BodyPart(Cursor cursor, BodyDescriptor parent) {
-            super(cursor, parent, T_START_BODYPART, T_END_BODYPART);
-        }
-    }
-    
     private final boolean strictParsing;
     private final boolean maximalBodyDescriptor;
-    private int state = T_END_OF_STREAM;
-    private Cursor cursor;
-    private StateMachine currentStateMachine;
-    private final List entities = new ArrayList();
+    private final LinkedList entities = new LinkedList();
     
+    private int state = T_END_OF_STREAM;
+    private EntityStateMachine currentStateMachine;
     private int recursionMode = M_RECURSE;
+    private InputBuffer inbuffer;
+    private RootInputStream rootInputStream;
     
     /**
      * Constructs a standard (lax) stream.
@@ -584,26 +127,57 @@
      * internal state.
      */
     public void parse(InputStream stream) {
-        entities.clear();
-        cursor = new StreamCursor(stream);
-        state = parseMessage(cursor, null);
+        doParse(stream, null);
     }
 
-    private int parseMessage(Cursor cursor, BodyDescriptor parent) {
+    /** Instructs the {@code MimeTokenStream} to parse the given content with 
+     * the content type. The message stream is assumed to have no message header
+     * and is expected to begin with a message body. This can be the case when 
+     * the message content is transmitted using a different transport protocol 
+     * such as HTTP.
+     * <p/>
+     * If the {@code MimeTokenStream} has already been in use, resets the streams
+     * internal state.
+     */    
+    public void parseHeadless(InputStream stream, String contentType) {
+        if (contentType == null) {
+            throw new IllegalArgumentException("Content type may not be null");
+        }
+        doParse(stream, contentType);
+    }
+
+    private void doParse(InputStream stream, String contentType) {
+        entities.clear();
+        rootInputStream = new RootInputStream(stream);
+        inbuffer = new InputBuffer(rootInputStream, 4 * 1024);
         switch (recursionMode) {
-            case M_RAW:
-                currentStateMachine = new RawEntity(cursor.nextSection());
-                break;
-            case M_NO_RECURSE:
-                // expected to be called only at start of paring
-            case M_RECURSE:
-                currentStateMachine = new Message(cursor, parent);
-                break;
+        case M_RAW:
+            RawEntity rawentity = new RawEntity(new BufferingInputStream(inbuffer));
+            currentStateMachine = rawentity;
+            break;
+        case M_NO_RECURSE:
+            // expected to be called only at start of paring
+        case M_RECURSE:
+            MimeEntity mimeentity = new MimeEntity(
+                    rootInputStream,
+                    new BufferingInputStream(inbuffer),
+                    inbuffer,
+                    null, 
+                    T_START_MESSAGE, 
+                    T_END_MESSAGE,
+                    maximalBodyDescriptor,
+                    strictParsing);
+            mimeentity.setRecursionMode(recursionMode);
+            if (contentType != null) {
+                mimeentity.skipHeader(contentType);
+            }
+            currentStateMachine = mimeentity;
+            break;
         }
         entities.add(currentStateMachine);
-        return currentStateMachine.state;
+        state = currentStateMachine.getState();
     }
-    
+
     /**
      * Determines if this parser is currently in raw mode.
      * 
@@ -616,25 +190,6 @@
     }
     
     /**
-     * Enables or disables raw mode. In raw mode all future entities 
-     * (messages or body parts) in the stream will be reported to the
-     * {@link ContentHandler#raw(InputStream)} handler method only.
-     * The stream will contain the entire unparsed entity contents 
-     * including header fields and whatever is in the body.
-     * 
-     * @param raw <code>true</code> enables raw mode, <code>false</code>
-     *        disables it.
-     * @deprecated pass {@link #M_RAW} to {@link #setRecursionMode(int)} 
-     */
-    public void setRaw(boolean raw) {
-        if (raw) {
-            recursionMode = M_RAW;
-        } else {
-            recursionMode = M_RECURSE;
-        }
-    }
-    
-    /**
      * Gets the current recursion mode.
      * The recursion mode specifies the approach taken to parsing parts.
      * {@link #M_RAW}  mode does not parse the part at all.
@@ -657,7 +212,8 @@
      * @param mode {@link #M_RECURSE}, {@link #M_RAW} or {@link #M_NO_RECURSE}
      */
     public void setRecursionMode(int mode) {
-        this.recursionMode = mode;
+        recursionMode = mode;
+        currentStateMachine.setRecursionMode(mode);
     }
 
     /**
@@ -673,7 +229,8 @@
      * {@link ContentHandler#startMessage()}, etc.
      */
     public void stop() {
-        cursor.stop();
+        inbuffer.clear();
+        rootInputStream.truncate();
     }
 
     /**
@@ -684,115 +241,6 @@
     }
 
     /**
-     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
-     * @return String with the fields raw contents.
-     * @throws IllegalStateException {@link #getState()} returns another
-     *   value than {@link #T_FIELD}.
-     */
-    public String getField() {
-        switch (getState()) {
-            case T_FIELD:
-                return ((Entity) currentStateMachine).field;
-            default:
-                throw new IllegalStateException("Expected state to be T_FIELD.");
-        }
-    }
-
-    /**
-     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
-     * @return String with the fields name.
-     * @throws IllegalStateException {@link #getState()} returns another
-     *   value than {@link #T_FIELD}.
-     */
-    public String getFieldName() {
-        switch (getState()) {
-            case T_FIELD:
-                return ((Entity) currentStateMachine).fieldName;
-            default:
-                throw new IllegalStateException("Expected state to be T_FIELD.");
-        }
-    }
-
-    /**
-     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
-     * @return String with the fields value.
-     * @throws IllegalStateException {@link #getState()} returns another
-     *   value than {@link #T_FIELD}.
-     */
-    public String getFieldValue() {
-        switch (getState()) {
-            case T_FIELD:
-                return ((Entity) currentStateMachine).fieldValue;
-            default:
-                throw new IllegalStateException("Expected state to be T_FIELD.");
-        }
-    }
-
-    /**
-     * Monitors the given event.
-     * Subclasses may override to perform actions upon events.
-     * Base implementation logs at warn.
-     * @param event <code>Event</code>, not null
-     * @throws MimeException subclasses may elect to throw this exception upon
-     * invalid content
-     * @throws IOException subclasses may elect to throw this exception
-     */
-    protected void monitor(Event event) throws MimeException, IOException {
-        if (strictParsing) {
-            throw new MimeParseEventException(event);
-        } else {
-            warn(event);
-        }
-    }
-    
-    /**
-     * Creates an indicative message suitable for display
-     * based on the given event and the current state of the system.
-     * @param event <code>Event</code>, not null
-     * @return message suitable for use as a message in an exception
-     * or for logging
-     */
-    protected String message(Event event) {
-        String preamble = "";
-        try {
-            preamble = "Line " + cursor.getLineNumber() + ": ";
-        } catch (IOException e) {
-            log.debug("Cannot get event line number.", e);
-        }
-
-        final String message;
-        if (event == null) {
-            message = "Event is unexpectedly null.";
-        } else {
-            message = event.toString();
-        }
-        final String result = preamble + message;
-        return result;
-    }
-    
-    /**
-     * Logs (at warn) an indicative message based on the given event 
-     * and the current state of the system.
-     * @param event <code>Event</code>, not null
-     */
-    protected void warn(Event event) {
-        if (log.isWarnEnabled()) {
-            log.warn(message(event));
-        }
-    }
-    
-    /**
-     * Logs (at debug) an indicative message based on the given event
-     * and the current state of the system.
-     * @param event <code>Event</code>, not null
-     */
-    protected void debug(Event event) {
-        if (log.isDebugEnabled()) {
-            log.debug(message(event));
-        }
-    }
-
-    /**
      * This method is valid, if {@link #getState()} returns either of
      * {@link #T_RAW_ENTITY}, {@link #T_PREAMBLE}, or {@link #T_EPILOGUE}.
      * It returns the raw entity, preamble, or epilogue contents.
@@ -801,7 +249,7 @@
      *   invalid value.
      */
     public InputStream getInputStream() {
-        return currentStateMachine.read();
+        return currentStateMachine.getContentStream();
     }
 
     /**
@@ -857,18 +305,40 @@
      * @return <code>BodyDescriptor</code>, not nulls
      */
     public BodyDescriptor getBodyDescriptor() {
-        switch (getState()) {
-            case T_BODY:
-            case T_START_MULTIPART:
-            case T_PREAMBLE:
-            case T_EPILOGUE:
-                return ((Entity) currentStateMachine).body;
-            default:
-                throw new IllegalStateException("Expected state to be T_BODY.");
-        }
+        return currentStateMachine.getBodyDescriptor();
     }
 
     /**
+     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
+     * @return String with the fields raw contents.
+     * @throws IllegalStateException {@link #getState()} returns another
+     *   value than {@link #T_FIELD}.
+     */
+    public String getField() {
+        return currentStateMachine.getField();
+    }
+    
+    /**
+     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
+     * @return String with the fields name.
+     * @throws IllegalStateException {@link #getState()} returns another
+     *   value than {@link #T_FIELD}.
+     */
+    public String getFieldName() {
+        return currentStateMachine.getFieldName();
+    }
+
+    /**
+     * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
+     * @return String with the fields value.
+     * @throws IllegalStateException {@link #getState()} returns another
+     *   value than {@link #T_FIELD}.
+     */
+    public String getFieldValue() {
+        return currentStateMachine.getFieldValue();
+    }
+
+    /**
      * This method advances the token stream to the next token.
      * @throws IllegalStateException The method has been called, although
      *   {@link #getState()} was already {@link #T_END_OF_STREAM}.
@@ -878,15 +348,20 @@
             throw new IllegalStateException("No more tokens are available.");
         }
         while (currentStateMachine != null) {
-            state = currentStateMachine.next();
+            EntityStateMachine next = currentStateMachine.advance();
+            if (next != null) {
+                entities.add(next);
+                currentStateMachine = next;
+            }
+            state = currentStateMachine.getState();
             if (state != T_END_OF_STREAM) {
                 return state;
             }
-            entities.remove(entities.size()-1);
-            if (entities.size() == 0) {
+            entities.removeLast();
+            if (entities.isEmpty()) {
                 currentStateMachine = null;
             } else {
-                currentStateMachine = (StateMachine) entities.get(entities.size()-1);
+                currentStateMachine = (EntityStateMachine) entities.getLast();
             }
         }
         state = T_END_OF_STREAM;
@@ -894,17 +369,11 @@
     }
 
     /**
-     * Creates a new instance of {@link BodyDescriptor}. Subclasses may override
-     * this in order to create body descriptors, that provide more specific
-     * information.
+     * Renders a state as a string suitable for logging.
+     * @param state 
+     * @return rendered as string, not null
      */
-    protected MutableBodyDescriptor newBodyDescriptor(BodyDescriptor pParent) {
-        final MutableBodyDescriptor result;
-        if (maximalBodyDescriptor) {
-            result = new MaximalBodyDescriptor(pParent);
-        } else {
-            result = new DefaultBodyDescriptor(pParent);
-        }
-        return result;
+    public static final String stateToString(int state) {
+        return AbstractEntity.stateToString(state);
     }
 }
Index: src/main/java/org/apache/james/mime4j/MimeStreamParser.java
===================================================================
--- src/main/java/org/apache/james/mime4j/MimeStreamParser.java	(revision 674082)
+++ src/main/java/org/apache/james/mime4j/MimeStreamParser.java	(working copy)
@@ -33,7 +33,7 @@
  *      ContentHandler handler = new MyHandler();
  *      MimeStreamParser parser = new MimeStreamParser();
  *      parser.setContentHandler(handler);
- *      parser.parse(new BufferedInputStream(new FileInputStream("mime.msg")));
+ *      parser.parse(new FileInputStream("mime.msg"));
  * </pre>
  * <strong>NOTE:</strong> All lines must end with CRLF 
  * (<code>\r\n</code>). If you are unsure of the line endings in your stream 
Index: src/main/java/org/apache/james/mime4j/InputBuffer.java
===================================================================
--- src/main/java/org/apache/james/mime4j/InputBuffer.java	(revision 674082)
+++ src/main/java/org/apache/james/mime4j/InputBuffer.java	(working copy)
@@ -181,6 +181,11 @@
         return chunk;
     }
 
+    public void clear() {
+        this.bufpos = 0;
+        this.buflen = 0;
+    }
+    
     public String toString() {
         StringBuffer buffer = new StringBuffer();
         buffer.append("[pos: ");
Index: src/main/java/org/apache/james/mime4j/StreamCursor.java
===================================================================
--- src/main/java/org/apache/james/mime4j/StreamCursor.java	(revision 674082)
+++ src/main/java/org/apache/james/mime4j/StreamCursor.java	(working copy)
@@ -1,163 +0,0 @@
-/****************************************************************
- * 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;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.james.mime4j.decoder.Base64InputStream;
-import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
-
-/**
- * Stream based cursor. 
- */
-public class StreamCursor implements Cursor {
-
-    protected final InputBuffer buffer;
-    protected final BufferingInputStream bufferingInputStream;
-    protected final RootInputStream rootInputStream;
-    
-    protected MimeBoundaryInputStream mbis;
-    protected InputStream contentStream;
-    
-    /**
-     * Constructs a child cursor.
-     * @param parent <code>Cursor</code>, not null 
-     */
-    StreamCursor(StreamCursor parent) {
-        this.buffer = parent.buffer;
-        this.bufferingInputStream = parent.bufferingInputStream;
-        this.rootInputStream = parent.rootInputStream;
-    }
-    
-    /**
-     * Constructs a new cursor from the given root contents.
-     * @param stream <code>InputStream</code>, not null
-     */
-    StreamCursor(InputStream stream) {
-        this.buffer = new InputBuffer(stream, 1024 * 4);
-        this.bufferingInputStream = new BufferingInputStream(this.buffer);
-        this.rootInputStream = new RootInputStream(this.bufferingInputStream);
-        this.contentStream = this.rootInputStream;
-    }
-    
-    /**
-     * Constructs a cursor from the previous cursor.
-     * @param previous <code>StreamCursor</code>, not null
-     * @param contentStream <code>InputStream</code>, not null
-     */
-    StreamCursor(StreamCursor previous, InputStream contentStream) {
-        this.buffer = previous.buffer;
-        this.bufferingInputStream = previous.bufferingInputStream;
-        this.rootInputStream = previous.rootInputStream;
-        this.contentStream = contentStream;
-    }
-    
-    /**
-     * @see Cursor#stop()
-     */
-    public void stop() {
-        rootInputStream.truncate();
-    }
-    
-    /**
-     * @see Cursor#getLineNumber()
-     */
-    public int getLineNumber() {
-        return rootInputStream.getLineNumber();
-    }
-
-    /**
-     * @see Cursor#decodeBase64()
-     */
-    public Cursor decodeBase64() throws IOException {
-        return new StreamCursor(this, 
-                new EOLConvertingInputStream(new Base64InputStream(mbis)));
-    }
-    
-    /**
-     * @see Cursor#decodeQuotedPrintable()
-     */
-    public Cursor decodeQuotedPrintable() throws IOException {
-        return new StreamCursor(this, 
-                new EOLConvertingInputStream(new QuotedPrintableInputStream(mbis)));
-    }
-    
-    /**
-     * @see Cursor#advanceToBoundary()
-     */
-    public void advanceToBoundary() throws IOException {
-        byte[] tmp = new byte[2048];
-        while (mbis.read(tmp)!= -1) {
-        }
-    }
-
-    /**
-     * @see Cursor#isEnded()
-     */
-    public boolean isEnded() throws IOException {
-        return mbis.eof();
-    }
-
-    /**
-     * @see Cursor#moreMimeParts()
-     */
-    public boolean moreMimeParts() throws IOException {
-        return !mbis.isLastPart();
-    }
-
-    /**
-     * @see Cursor#boundary(String)
-     */
-    public void boundary(String boundary) throws IOException {
-        mbis = new MimeBoundaryInputStream(buffer, boundary);
-        contentStream = new CloseShieldInputStream(mbis);
-    }
-
-    /**
-     * @see Cursor#nextMimePartCursor()
-     */
-    public Cursor nextMimePartCursor() {
-        return new StreamCursor(this, mbis);
-    }
-
-    /**
-     * @see Cursor#nextSection()
-     */
-    public InputStream nextSection() {
-        return contentStream;
-    }
-
-    /**
-     * @see Cursor#advance()
-     */
-    public byte advance() throws IOException {
-        return (byte) nextSection().read();
-    }
-
-    public InputStream rest() {
-        return contentStream;
-    }
-
-    public InputStream root() {
-        return rootInputStream;
-    }
-    
-}
