james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rdon...@apache.org
Subject svn commit: r674206 [1/2] - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/ test/java/org/apache/james/mime4j/
Date Sat, 05 Jul 2008 15:31:18 GMT
Author: rdonkin
Date: Sat Jul  5 08:31:17 2008
New Revision: 674206

URL: http://svn.apache.org/viewvc?rev=674206&view=rev
Log:
Second patch from MIM4J-5 https://issues.apache.org/jira/browse/MIME4J-5. Contributed by Oleg Kalnichevski. Factors out an abstract superclass and interface for pull parser state machine. This will ease the creation of alternatives to MimeTokenStream. Factors state constants into separate classes to improve readability. Removes Cursor. This was originally added to allow first class support for nio. The design cost of this abstract is higher than the likely performance gain.

Added:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/AbstractEntity.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStateMachine.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStates.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Event.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeEntity.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RawEntity.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RecursionMode.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MimeEntityTest.java
Modified:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeParseEventException.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeStreamParser.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/StreamCursor.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/MultipartTokensTest.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/StrictMimeTokenStreamTest.java

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/AbstractEntity.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/AbstractEntity.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/AbstractEntity.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/AbstractEntity.java Sat Jul  5 08:31:17 2008
@@ -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;
+    }
+    
+}

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java?rev=674206&r1=674205&r2=674206&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Cursor.java Sat Jul  5 08:31:17 2008
@@ -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();
-
-}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EOFSensitiveInputStream.java Sat Jul  5 08:31:17 2008
@@ -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;
+    }
+
+}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStateMachine.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStateMachine.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStateMachine.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStateMachine.java Sat Jul  5 08:31:17 2008
@@ -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;
+    
+}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStates.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStates.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStates.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/EntityStates.java Sat Jul  5 08:31:17 2008
@@ -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;
+
+}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Event.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Event.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Event.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/Event.java Sat Jul  5 08:31:17 2008
@@ -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

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java?rev=674206&r1=674205&r2=674206&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/InputBuffer.java Sat Jul  5 08:31:17 2008
@@ -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: ");

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeEntity.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeEntity.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeEntity.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeEntity.java Sat Jul  5 08:31:17 2008
@@ -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));
+        }
+    }
+
+}

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeParseEventException.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeParseEventException.java?rev=674206&r1=674205&r2=674206&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeParseEventException.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeParseEventException.java Sat Jul  5 08:31:17 2008
@@ -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;
     }
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeStreamParser.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeStreamParser.java?rev=674206&r1=674205&r2=674206&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeStreamParser.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeStreamParser.java Sat Jul  5 08:31:17 2008
@@ -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 

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java?rev=674206&r1=674205&r2=674206&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/MimeTokenStream.java Sat Jul  5 08:31:17 2008
@@ -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,178 +77,7 @@
  * 
  * @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;
-    
-    /**
-     * 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;
-    }
+public class MimeTokenStream implements EntityStates, RecursionMode {
     
     /**
      * Creates a stream that creates a more detailed body descriptor.
@@ -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);
+    }
+
+    /** 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 int parseMessage(Cursor cursor, BodyDescriptor parent) {
+    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,15 +305,37 @@
      * @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();
     }
 
     /**
@@ -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.
-     */
-    protected MutableBodyDescriptor newBodyDescriptor(BodyDescriptor pParent) {
-        final MutableBodyDescriptor result;
-        if (maximalBodyDescriptor) {
-            result = new MaximalBodyDescriptor(pParent);
-        } else {
-            result = new DefaultBodyDescriptor(pParent);
-        }
-        return result;
+     * Renders a state as a string suitable for logging.
+     * @param state 
+     * @return rendered as string, not null
+     */
+    public static final String stateToString(int state) {
+        return AbstractEntity.stateToString(state);
     }
 }

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RawEntity.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RawEntity.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RawEntity.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RawEntity.java Sat Jul  5 08:31:17 2008
@@ -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;
+    }
+    
+}

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RecursionMode.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RecursionMode.java?rev=674206&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RecursionMode.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/RecursionMode.java Sat Jul  5 08:31:17 2008
@@ -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;
+    
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


Mime
View raw message