james-mime4j-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject svn commit: r1089090 - in /james/mime4j/trunk: core/src/main/java/org/apache/james/mime4j/stream/ core/src/test/java/org/apache/james/mime4j/stream/ dom/src/main/java/org/apache/james/mime4j/message/
Date Tue, 05 Apr 2011 15:49:38 GMT
Author: olegk
Date: Tue Apr  5 15:49:38 2011
New Revision: 1089090

URL: http://svn.apache.org/viewvc?rev=1089090&view=rev
Log:
MIME4J-145: Moved code splitting raw field value into a list of name/value pairs from DefaultBodyDescriptor to RawFieldParser; replaced old implementation with a new one based on code borrowed from HttpCore; the new implementation should generate less intermediate garbage on the heap

Added:
    james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java   (with props)
    james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java   (with props)
    james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java   (with props)
Modified:
    james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java
    james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawField.java
    james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
    james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/BaseTestForBodyDescriptors.java
    james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
    james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/MaximalBodyDescriptor.java

Modified: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java?rev=1089090&r1=1089089&r2=1089090&view=diff
==============================================================================
--- james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java (original)
+++ james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/DefaultBodyDescriptor.java Tue Apr  5 15:49:38 2011
@@ -20,6 +20,7 @@
 package org.apache.james.mime4j.stream;
 
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 
 import org.apache.james.mime4j.MimeException;
@@ -101,39 +102,45 @@ public class DefaultBodyDescriptor imple
      * @param field the MIME field.
      */
     public void addField(RawField field) throws MimeException {
-        String name = field.getName();
-        String value = field.getBody();
-
-        name = name.trim().toLowerCase();
+        String name = field.getName().toLowerCase(Locale.US);
         
         if (name.equals("content-transfer-encoding") && !contentTransferEncSet) {
             contentTransferEncSet = true;
-            
-            value = value.trim().toLowerCase();
-            if (value.length() > 0) {
-                transferEncoding = value;
+            String value = field.getBody();
+            if (value != null) {
+                value = value.trim().toLowerCase(Locale.US);
+                if (value.length() > 0) {
+                    transferEncoding = value;
+                }
             }
-            
         } else if (name.equals("content-length") && contentLength == -1) {
-            try {
-                contentLength = Long.parseLong(value.trim());
-            } catch (NumberFormatException e) {
-                if (monitor.warn("Invalid content length: " + value, 
-                        "ignoring Content-Length header")) {
-                    throw new MimeException("Invalid Content-Length header: " + value);
+            String value = field.getBody();
+            if (value != null) {
+                value = value.trim();
+                try {
+                    contentLength = Long.parseLong(value.trim());
+                } catch (NumberFormatException e) {
+                    if (monitor.warn("Invalid content length: " + value, 
+                            "ignoring Content-Length header")) {
+                        throw new MimeException("Invalid Content-Length header: " + value);
+                    }
                 }
             }
         } else if (name.equals("content-type") && !contentTypeSet) {
-            parseContentType(value);
+            parseContentType(field);
         }
     }
 
-    private void parseContentType(String value) throws MimeException {
+    private void parseContentType(RawField field) throws MimeException {
         contentTypeSet = true;
+        RawBody body = RawFieldParser.DEFAULT.parseRawBody(field);
+        String main = body.getValue();
+        Map<String, String> params = new HashMap<String, String>();
+        for (NameValuePair nmp: body.getParams()) {
+            String name = nmp.getName().toLowerCase(Locale.US);
+            params.put(name, nmp.getValue());
+        }
         
-        Map<String, String> params = DefaultBodyDescriptor.getHeaderParams(value, getDecodeMonitor());
-        
-        String main = params.get("");
         String type = null;
         String subtype = null;
         if (main != null) {
@@ -185,7 +192,6 @@ public class DefaultBodyDescriptor imple
          * Add all other parameters to parameters.
          */
         parameters.putAll(params);
-        parameters.remove("");
         parameters.remove("boundary");
         parameters.remove("charset");
     }
@@ -252,187 +258,4 @@ public class DefaultBodyDescriptor imple
         return subType;
     }
 
-    /**
-     * <p>Parses a complex field value into a map of key/value pairs. You may
-     * use this, for example, to parse a definition like
-     * <pre>
-     *   text/plain; charset=UTF-8; boundary=foobar
-     * </pre>
-     * The above example would return a map with the keys "", "charset",
-     * and "boundary", and the values "text/plain", "UTF-8", and "foobar".
-     * </p><p>
-     * Header value will be unfolded and excess white space trimmed.
-     * </p>
-     * @param pValue The field value to parse.
-     * @return The result map; use the key "" to retrieve the first value.
-     */
-    public static Map<String, String> getHeaderParams(
-            String pValue, DecodeMonitor monitor) throws MimeException {
-        pValue = pValue.trim();
-        
-        Map<String, String> result = new HashMap<String, String>();
-    
-        // split main value and parameters
-        String main;
-        String rest;
-        if (pValue.indexOf(";") == -1) {
-            main = pValue;
-            rest = null;
-        } else {
-            main = pValue.substring(0, pValue.indexOf(";"));
-            rest = pValue.substring(main.length() + 1);
-        }
-    
-        result.put("", main);
-        if (rest != null) {
-            char[] chars = rest.toCharArray();
-            StringBuilder paramName = new StringBuilder(64);
-            StringBuilder paramValue = new StringBuilder(64);
-    
-            final byte READY_FOR_NAME = 0;
-            final byte IN_NAME = 1;
-            final byte READY_FOR_VALUE = 2;
-            final byte IN_VALUE = 3;
-            final byte IN_QUOTED_VALUE = 4;
-            final byte VALUE_DONE = 5;
-            final byte ERROR = 99;
-    
-            byte state = READY_FOR_NAME;
-            boolean escaped = false;
-            for (char c : chars) {
-                switch (state) {
-                    case ERROR:
-                        if (c == ';')
-                            state = READY_FOR_NAME;
-                        break;
-    
-                    case READY_FOR_NAME:
-                        if (c == '=') {
-                            if (monitor.warn("Expected header param name, got '='", "ignoring")) {
-                                throw new MimeException("Expected header param name, got '='");
-                            }
-                            state = ERROR;
-                            break;
-                        }
-    
-                        paramName.setLength(0);
-                        paramValue.setLength(0);
-    
-                        state = IN_NAME;
-                        // fall-through
-    
-                    case IN_NAME:
-                        if (c == '=') {
-                            if (paramName.length() == 0)
-                                state = ERROR;
-                            else
-                                state = READY_FOR_VALUE;
-                            break;
-                        }
-    
-                        // not '='... just add to name
-                        paramName.append(c);
-                        break;
-    
-                    case READY_FOR_VALUE:
-                        boolean fallThrough = false;
-                        switch (c) {
-                            case ' ':
-                            case '\t':
-                                break;  // ignore spaces, especially before '"'
-    
-                            case '"':
-                                state = IN_QUOTED_VALUE;
-                                break;
-    
-                            default:
-                                state = IN_VALUE;
-                                fallThrough = true;
-                                break;
-                        }
-                        if (!fallThrough)
-                            break;
-    
-                        // fall-through
-    
-                    case IN_VALUE:
-                        fallThrough = false;
-                        switch (c) {
-                            case ';':
-                            case ' ':
-                            case '\t':
-                                result.put(
-                                   paramName.toString().trim().toLowerCase(),
-                                   paramValue.toString().trim());
-                                state = VALUE_DONE;
-                                fallThrough = true;
-                                break;
-                            default:
-                                paramValue.append(c);
-                                break;
-                        }
-                        if (!fallThrough)
-                            break;
-    
-                    case VALUE_DONE:
-                        switch (c) {
-                            case ';':
-                                state = READY_FOR_NAME;
-                                break;
-    
-                            case ' ':
-                            case '\t':
-                                break;
-    
-                            default:
-                                state = ERROR;
-                                break;
-                        }
-                        break;
-                        
-                    case IN_QUOTED_VALUE:
-                        switch (c) {
-                            case '"':
-                                if (!escaped) {
-                                    // don't trim quoted strings; the spaces could be intentional.
-                                    result.put(
-                                            paramName.toString().trim().toLowerCase(),
-                                            paramValue.toString());
-                                    state = VALUE_DONE;
-                                } else {
-                                    escaped = false;
-                                    paramValue.append(c);                                    
-                                }
-                                break;
-                                
-                            case '\\':
-                                if (escaped) {
-                                    paramValue.append('\\');
-                                }
-                                escaped = !escaped;
-                                break;
-    
-                            default:
-                                if (escaped) {
-                                    paramValue.append('\\');
-                                }
-                                escaped = false;
-                                paramValue.append(c);
-                                break;
-                        }
-                        break;
-    
-                }
-            }
-    
-            // done looping.  check if anything is left over.
-            if (state == IN_VALUE) {
-                result.put(
-                        paramName.toString().trim().toLowerCase(),
-                        paramValue.toString().trim());
-            }
-        }
-    
-        return result;
-    }
 }

Added: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java?rev=1089090&view=auto
==============================================================================
--- james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java (added)
+++ james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java Tue Apr  5 15:49:38 2011
@@ -0,0 +1,88 @@
+/****************************************************************
+ * 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.stream;
+
+import org.apache.james.mime4j.util.LangUtils;
+
+public final class NameValuePair {
+
+    private final String name;
+    private final String value;
+    private final boolean quoted;
+
+    public NameValuePair(final String name, final String value, boolean quoted) {
+        super();
+        if (name == null) {
+            throw new IllegalArgumentException("Name may not be null");
+        }
+        this.name = name;
+        this.value = value;
+        this.quoted = quoted;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public boolean isQuoted() {
+        return this.quoted;
+    }
+
+    public String toString() {
+        if (this.value == null) {
+            return name;
+        } else {
+            StringBuilder buffer = new StringBuilder();
+            buffer.append(this.name);
+            buffer.append("=");
+            if (this.quoted) {
+                buffer.append("\"");
+            }
+            buffer.append(this.value);
+            if (this.quoted) {
+                buffer.append("\"");
+            }
+            return buffer.toString();
+        }
+    }
+
+    public boolean equals(final Object object) {
+        if (this == object) return true;
+        if (object instanceof NameValuePair) {
+            NameValuePair that = (NameValuePair) object;
+            return this.name.equals(that.name)
+                  && LangUtils.equals(this.value, that.value);
+        } else {
+            return false;
+        }
+    }
+
+    public int hashCode() {
+        int hash = LangUtils.HASH_SEED;
+        hash = LangUtils.hashCode(hash, this.name);
+        hash = LangUtils.hashCode(hash, this.value);
+        return hash;
+    }
+
+}

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/NameValuePair.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java?rev=1089090&view=auto
==============================================================================
--- james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java (added)
+++ james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java Tue Apr  5 15:49:38 2011
@@ -0,0 +1,88 @@
+/****************************************************************
+ * 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.stream;
+
+/**
+ * This class represents a context of a parsing operation:
+ * <ul>
+ *  <li>the current position the parsing operation is expected to start at</li>
+ *  <li>the bounds limiting the scope of the parsing operation</li>
+ * </ul>
+ * <p/>
+ * Copied from Apache HttpCore project
+ */
+class ParserCursor {
+
+    private final int lowerBound;
+    private final int upperBound;
+    private int pos;
+
+    public ParserCursor(int lowerBound, int upperBound) {
+        super();
+        if (lowerBound < 0) {
+            throw new IndexOutOfBoundsException("Lower bound cannot be negative");
+        }
+        if (lowerBound > upperBound) {
+            throw new IndexOutOfBoundsException("Lower bound cannot be greater then upper bound");
+        }
+        this.lowerBound = lowerBound;
+        this.upperBound = upperBound;
+        this.pos = lowerBound;
+    }
+
+    public int getLowerBound() {
+        return this.lowerBound;
+    }
+
+    public int getUpperBound() {
+        return this.upperBound;
+    }
+
+    public int getPos() {
+        return this.pos;
+    }
+
+    public void updatePos(int pos) {
+        if (pos < this.lowerBound) {
+            throw new IndexOutOfBoundsException("pos: "+pos+" < lowerBound: "+this.lowerBound);
+        }
+        if (pos > this.upperBound) {
+            throw new IndexOutOfBoundsException("pos: "+pos+" > upperBound: "+this.upperBound);
+        }
+        this.pos = pos;
+    }
+
+    public boolean atEnd() {
+        return this.pos >= this.upperBound;
+    }
+
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append('[');
+        buffer.append(Integer.toString(this.lowerBound));
+        buffer.append('>');
+        buffer.append(Integer.toString(this.pos));
+        buffer.append('>');
+        buffer.append(Integer.toString(this.upperBound));
+        buffer.append(']');
+        return buffer.toString();
+    }
+
+}

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/ParserCursor.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java?rev=1089090&view=auto
==============================================================================
--- james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java (added)
+++ james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java Tue Apr  5 15:49:38 2011
@@ -0,0 +1,58 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.stream;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class RawBody {
+
+    private final String value;
+    private final List<NameValuePair> params;
+
+    RawBody(final String value, final List<NameValuePair> params) {
+        if (value == null) {
+            throw new IllegalArgumentException("Field value not be null");
+        }
+        this.value = value;
+        this.params = params != null ? params : new ArrayList<NameValuePair>();
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    public List<NameValuePair> getParams() {
+        return new ArrayList<NameValuePair>(this.params);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append(this.value);
+        buf.append("; ");
+        for (NameValuePair param: this.params) {
+            buf.append("; ");
+            buf.append(param);
+        }
+        return buf.toString();
+    }
+
+}

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawBody.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawField.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawField.java?rev=1089090&r1=1089089&r2=1089090&view=diff
==============================================================================
--- james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawField.java (original)
+++ james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawField.java Tue Apr  5 15:49:38 2011
@@ -70,6 +70,10 @@ public final class RawField {
         return null;
     }
 
+    int getDelimiterIdx() {
+        return delimiterIdx;
+    }
+
     boolean isUsedObsoleteSyntax() {
         return obsolete;
     }

Modified: james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java?rev=1089090&r1=1089089&r2=1089090&view=diff
==============================================================================
--- james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java (original)
+++ james/mime4j/trunk/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java Tue Apr  5 15:49:38 2011
@@ -19,9 +19,12 @@
 
 package org.apache.james.mime4j.stream;
 
+import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.List;
 
 import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.ContentUtil;
 
@@ -33,6 +36,8 @@ public class RawFieldParser {
     static final int COLON   = ':';
     static final int SPACE   = 0x20;
     static final int TAB     = 0x09;
+    static final int CR      = 0x0d;
+    static final int LF      = 0x0a;
     
     private static final BitSet FIELD_CHARS = new BitSet();
     
@@ -77,4 +82,209 @@ public class RawFieldParser {
         return new RawField(raw, colonIdx, obsolete, name, null);
     }
 
+    public RawBody parseRawBody(final RawField field) {
+        ByteSequence buf = field.getRaw();
+        int pos = field.getDelimiterIdx() + 1; 
+        if (buf == null) {
+            String body = field.getBody();
+            if (body == null) {
+                return new RawBody("", null);
+            }
+            buf = ContentUtil.encode(body);
+            pos = 0;
+        }
+        ParserCursor cursor = new ParserCursor(pos, buf.length());
+        return parseRawBody(buf, cursor);
+    }
+    
+    static final int[] DELIMS = { ';' };
+
+    RawBody parseRawBody(final ByteSequence buf, final ParserCursor cursor) {
+        int pos = cursor.getPos();
+        int indexFrom = pos;
+        int indexTo = cursor.getUpperBound();
+        while (pos < indexTo) {
+            int ch = buf.byteAt(pos);
+            if (isOneOf(ch, DELIMS)) {
+                break;
+            }
+            pos++;
+        }
+        String value = copyTrimmed(buf, indexFrom, pos);
+        if (pos == indexTo) {
+            cursor.updatePos(pos);
+            return new RawBody(value, null);
+        }
+        cursor.updatePos(pos + 1);
+        List<NameValuePair> params = parseParameters(buf, cursor);
+        return new RawBody(value, params);
+    }
+    
+    List<NameValuePair> parseParameters(final ByteSequence buf, final ParserCursor cursor) {
+        List<NameValuePair> params = new ArrayList<NameValuePair>();
+        int pos = cursor.getPos();
+        int indexTo = cursor.getUpperBound();
+
+        while (pos < indexTo) {
+            int ch = buf.byteAt(pos);
+            if (isWhitespace(ch)) {
+                pos++;
+            } else {
+                break;
+            }
+        }
+        cursor.updatePos(pos);
+        if (cursor.atEnd()) {
+            return params;
+        }
+
+        while (!cursor.atEnd()) {
+            NameValuePair param = parseParameter(buf, cursor, DELIMS);
+            params.add(param);
+        }
+        return params;
+    }
+
+    NameValuePair parseParameter(final ByteSequence buf, final ParserCursor cursor) {
+        return parseParameter(buf, cursor, DELIMS);
+    }
+    
+    NameValuePair parseParameter(final ByteSequence buf, final ParserCursor cursor, final int[] delimiters) {
+        boolean terminated = false;
+
+        int pos = cursor.getPos();
+        int indexFrom = cursor.getPos();
+        int indexTo = cursor.getUpperBound();
+
+        // Find name
+        String name = null;
+        while (pos < indexTo) {
+            int ch = buf.byteAt(pos);
+            if (ch == '=') {
+                break;
+            }
+            if (isOneOf(ch, delimiters)) {
+                terminated = true;
+                break;
+            }
+            pos++;
+        }
+
+        if (pos == indexTo) {
+            terminated = true;
+            name = copyTrimmed(buf, indexFrom, indexTo);
+        } else {
+            name = copyTrimmed(buf, indexFrom, pos);
+            pos++;
+        }
+
+        if (terminated) {
+            cursor.updatePos(pos);
+            return new NameValuePair(name, null, false);
+        }
+
+        // Find value
+        String value = null;
+        int i1 = pos;
+
+        boolean qouted = false;
+        boolean escaped = false;
+        while (pos < indexTo) {
+            int ch = buf.byteAt(pos);
+            if (ch == '"' && !escaped) {
+                qouted = !qouted;
+            }
+            if (!qouted && !escaped && isOneOf(ch, delimiters)) {
+                terminated = true;
+                break;
+            }
+            if (escaped) {
+                escaped = false;
+            } else {
+                escaped = qouted && ch == '\\';
+            }
+            pos++;
+        }
+
+        int i2 = pos;
+        // Trim leading white spaces
+        while (i1 < i2 && (isWhitespace(buf.byteAt(i1)))) {
+            i1++;
+        }
+        // Trim trailing white spaces
+        while ((i2 > i1) && (isWhitespace(buf.byteAt(i2 - 1)))) {
+            i2--;
+        }
+        boolean quoted = false;
+        // Strip away quotes if necessary
+        if (((i2 - i1) >= 2)
+            && (buf.byteAt(i1) == '"')
+            && (buf.byteAt(i2 - 1) == '"')) {
+            quoted = true;
+            i1++;
+            i2--;
+        }
+        if (quoted) {
+            value = copyEscaped(buf, i1, i2);
+        } else {
+            value = copy(buf, i1, i2);
+        }
+        if (terminated) {
+            pos++;
+        }
+        cursor.updatePos(pos);
+        return new NameValuePair(name, value, quoted);
+    }
+    
+    private static boolean isOneOf(final int ch, final int[] chs) {
+        if (chs != null) {
+            for (int i = 0; i < chs.length; i++) {
+                if (ch == chs[i]) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    
+    private static boolean isWhitespace(int i) {
+        return i == SPACE || i == TAB || i == CR || i == LF;
+    }
+    
+    private static String copy(final ByteSequence buf, int beginIndex, int endIndex) {
+        return ContentUtil.decode(buf, beginIndex, endIndex - beginIndex);
+    }
+
+    private static String copyTrimmed(final ByteSequence buf, int beginIndex, int endIndex) {
+        while (beginIndex < endIndex && isWhitespace(buf.byteAt(beginIndex))) {
+            beginIndex++;
+        }
+        while (endIndex > beginIndex && isWhitespace(buf.byteAt(endIndex - 1))) {
+            endIndex--;
+        }
+        return ContentUtil.decode(buf, beginIndex, endIndex - beginIndex);
+    }
+
+    private static String copyEscaped(final ByteSequence buf, int beginIndex, int endIndex) {
+        ByteArrayBuffer copy = new ByteArrayBuffer(buf.length());
+        boolean escaped = false;
+        for (int i = beginIndex; i < endIndex; i++) {
+            int b = buf.byteAt(i);
+            if (escaped) {
+                if (b != '\"' && b != '\\') {
+                    copy.append('\\');
+                }
+                copy.append(b);
+                escaped = false;
+            } else {
+                if (b == '\\') {
+                    escaped = true;
+                } else {
+                    copy.append(b);
+                }
+            }
+        }
+        return ContentUtil.decode(copy, 0, copy.length());
+    }
+
 }

Modified: james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/BaseTestForBodyDescriptors.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/BaseTestForBodyDescriptors.java?rev=1089090&r1=1089089&r2=1089090&view=diff
==============================================================================
--- james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/BaseTestForBodyDescriptors.java (original)
+++ james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/BaseTestForBodyDescriptors.java Tue Apr  5 15:49:38 2011
@@ -167,7 +167,7 @@ public abstract class BaseTestForBodyDes
          */
         bd = newBodyDescriptor();
         bd.addField(new RawField("Content-Type", "multipart/yada; boundary=yada yada"));
-        assertEquals("yada", bd.getBoundary());
+        assertEquals("yada yada", bd.getBoundary());
         
         bd = newBodyDescriptor();
         bd.addField(new RawField("Content-Type", "multipart/yada; boUNdarY= ya:*da; \tcharset\t =  big5"));

Modified: james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java?rev=1089090&r1=1089089&r2=1089090&view=diff
==============================================================================
--- james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java (original)
+++ james/mime4j/trunk/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java Tue Apr  5 15:49:38 2011
@@ -19,6 +19,8 @@
 
 package org.apache.james.mime4j.stream;
 
+import java.util.List;
+
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.ContentUtil;
@@ -31,9 +33,9 @@ public class RawFieldParserTest extends 
     public void testBasicParsing() throws Exception {
         String s = "raw: stuff;\r\n  more stuff";
         ByteSequence raw = ContentUtil.encode(s);
-        
+
         RawFieldParser parser = new RawFieldParser();
-        
+
         RawField field = parser.parseField(raw);
         Assert.assertSame(raw, field.getRaw());
         Assert.assertEquals("raw", field.getName());
@@ -45,9 +47,9 @@ public class RawFieldParserTest extends 
     public void testParsingObsoleteSyntax() throws Exception {
         String s = "raw  \t  : stuff;\r\n  more stuff";
         ByteSequence raw = ContentUtil.encode(s);
-        
+
         RawFieldParser parser = new RawFieldParser();
-        
+
         RawField field = parser.parseField(raw);
         Assert.assertSame(raw, field.getRaw());
         Assert.assertEquals("raw", field.getName());
@@ -59,9 +61,9 @@ public class RawFieldParserTest extends 
     public void testParsingInvalidSyntax1() throws Exception {
         String s = "raw    stuff;\r\n  more stuff";
         ByteSequence raw = ContentUtil.encode(s);
-        
+
         RawFieldParser parser = new RawFieldParser();
-        
+
         try {
             parser.parseField(raw);
             fail("MimeException should have been thrown");
@@ -72,9 +74,9 @@ public class RawFieldParserTest extends 
     public void testParsingInvalidSyntax2() throws Exception {
         String s = "raw    \t \t";
         ByteSequence raw = ContentUtil.encode(s);
-        
+
         RawFieldParser parser = new RawFieldParser();
-        
+
         try {
             parser.parseField(raw);
             fail("MimeException should have been thrown");
@@ -82,4 +84,170 @@ public class RawFieldParserTest extends 
         }
     }
 
-}
+    public void testNameValueParseBasics() {
+        RawFieldParser parser = new RawFieldParser();
+        String s = "test";
+        ByteSequence buf = ContentUtil.encode(s);
+        ParserCursor cursor = new ParserCursor(0, s.length());
+
+        NameValuePair param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals(null, param.getValue());
+        assertEquals(s.length(), cursor.getPos());
+        assertTrue(cursor.atEnd());
+
+        s = "test;";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals(null, param.getValue());
+        assertEquals(s.length(), cursor.getPos());
+        assertTrue(cursor.atEnd());
+
+        s = "test=stuff";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals("stuff", param.getValue());
+        assertEquals(s.length(), cursor.getPos());
+        assertTrue(cursor.atEnd());
+
+        s = "   test  =   stuff ";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals("stuff", param.getValue());
+        assertEquals(s.length(), cursor.getPos());
+        assertTrue(cursor.atEnd());
+
+        s = "   test  =   stuff ;1234";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals("stuff", param.getValue());
+        assertEquals(s.length() - 4, cursor.getPos());
+        assertFalse(cursor.atEnd());
+
+        s = "test  = \"stuff\"";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals("stuff", param.getValue());
+
+        s = "test  = \"  stuff\\\"\"";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals("  stuff\"", param.getValue());
+
+        s = "test  = \"  stuff\\\\\"\"";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals("  stuff\\\"", param.getValue());
+
+        s = "  test";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("test", param.getName());
+        assertEquals(null, param.getValue());
+
+        s = "  ";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("", param.getName());
+        assertEquals(null, param.getValue());
+
+        s = " = stuff ";
+        buf = ContentUtil.encode(s);
+        cursor = new ParserCursor(0, s.length());
+
+        param = parser.parseParameter(buf, cursor);
+        assertEquals("", param.getName());
+        assertEquals("stuff", param.getValue());
+    }
+
+    public void testNameValueListParseBasics() {
+        RawFieldParser parser = new RawFieldParser();
+        ByteSequence buf = ContentUtil.encode(
+                "test; test1 =  stuff   ; test2 =  \"stuff; stuff\"; test3=\"stuff");
+        ParserCursor cursor = new ParserCursor(0, buf.length());
+        List<NameValuePair> params = parser.parseParameters(buf, cursor);
+
+        assertEquals("test", params.get(0).getName());
+        assertEquals(null, params.get(0).getValue());
+        assertEquals("test1", params.get(1).getName());
+        assertEquals("stuff", params.get(1).getValue());
+        assertEquals("test2", params.get(2).getName());
+        assertEquals("stuff; stuff", params.get(2).getValue());
+        assertEquals("test3", params.get(3).getName());
+        assertEquals("\"stuff", params.get(3).getValue());
+        assertEquals(buf.length(), cursor.getPos());
+        assertTrue(cursor.atEnd());
+    }
+
+    public void testNameValueListParseEmpty() {
+        ByteSequence buf = ContentUtil.encode("    ");
+        RawFieldParser parser = new RawFieldParser();
+        ParserCursor cursor = new ParserCursor(0, buf.length());
+        List<NameValuePair> params = parser.parseParameters(buf, cursor);
+        assertEquals(0, params.size());
+    }
+
+    public void testNameValueListParseEscaped() {
+        ByteSequence buf = ContentUtil.encode(
+          "test1 =  \"\\\"stuff\\\"\"; test2= \"\\\\\"; test3 = \"stuff; stuff\"");
+        RawFieldParser parser = new RawFieldParser();
+        ParserCursor cursor = new ParserCursor(0, buf.length());
+        List<NameValuePair> params = parser.parseParameters(buf, cursor);
+        assertEquals(3, params.size());
+        assertEquals("test1", params.get(0).getName());
+        assertEquals("\"stuff\"", params.get(0).getValue());
+        assertEquals("test2", params.get(1).getName());
+        assertEquals("\\", params.get(1).getValue());
+        assertEquals("test3", params.get(2).getName());
+        assertEquals("stuff; stuff", params.get(2).getValue());
+    }
+
+    public void testRawBodyParse() {
+        ByteSequence buf = ContentUtil.encode(
+                "  text/plain ; charset=ISO-8859-1; "
+                + "boundary=foo; param1=value1; param2=\"value2\"; param3=value3");
+        RawFieldParser parser = new RawFieldParser();
+        ParserCursor cursor = new ParserCursor(0, buf.length());
+        RawBody body = parser.parseRawBody(buf, cursor);
+        assertNotNull(body);
+        assertEquals("text/plain", body.getValue());
+        List<NameValuePair> params = body.getParams();
+        assertEquals(5, params.size());
+        assertEquals("charset", params.get(0).getName());
+        assertEquals("ISO-8859-1", params.get(0).getValue());
+        assertEquals("boundary", params.get(1).getName());
+        assertEquals("foo", params.get(1).getValue());
+        assertEquals("param1", params.get(2).getName());
+        assertEquals("value1", params.get(2).getValue());
+        assertEquals("param2", params.get(3).getName());
+        assertEquals("value2", params.get(3).getValue());
+        assertEquals("param3", params.get(4).getName());
+        assertEquals("value3", params.get(4).getValue());
+    }
+
+}
\ No newline at end of file

Modified: james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/MaximalBodyDescriptor.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/MaximalBodyDescriptor.java?rev=1089090&r1=1089089&r2=1089090&view=diff
==============================================================================
--- james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/MaximalBodyDescriptor.java (original)
+++ james/mime4j/trunk/dom/src/main/java/org/apache/james/mime4j/message/MaximalBodyDescriptor.java Tue Apr  5 15:49:38 2011
@@ -21,7 +21,9 @@ package org.apache.james.mime4j.message;
 
 import java.io.StringReader;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import org.apache.james.mime4j.MimeException;
@@ -34,8 +36,11 @@ import org.apache.james.mime4j.field.mim
 import org.apache.james.mime4j.field.structured.parser.StructuredFieldParser;
 import org.apache.james.mime4j.stream.BodyDescriptor;
 import org.apache.james.mime4j.stream.DefaultBodyDescriptor;
+import org.apache.james.mime4j.stream.RawBody;
 import org.apache.james.mime4j.stream.MutableBodyDescriptor;
+import org.apache.james.mime4j.stream.NameValuePair;
 import org.apache.james.mime4j.stream.RawField;
+import org.apache.james.mime4j.stream.RawFieldParser;
 import org.apache.james.mime4j.util.MimeUtil;
 
 /**
@@ -115,37 +120,37 @@ public class MaximalBodyDescriptor exten
 
     @Override
     public void addField(RawField field) throws MimeException {
-        String name = field.getName();
-        String value = field.getBody();
-        name = name.trim().toLowerCase();
+        String name = field.getName().toLowerCase(Locale.US);;
         if (MimeUtil.MIME_HEADER_MIME_VERSION.equals(name) && !isMimeVersionSet) {
-            parseMimeVersion(value);
+            parseMimeVersion(field);
         } else if (MimeUtil.MIME_HEADER_CONTENT_ID.equals(name) && !isContentIdSet) {
-            parseContentId(value);
+            parseContentId(field);
         } else if (MimeUtil.MIME_HEADER_CONTENT_DESCRIPTION.equals(name) && !isContentDescriptionSet) {
-            parseContentDescription(value);
+            parseContentDescription(field);
         } else if (MimeUtil.MIME_HEADER_CONTENT_DISPOSITION.equals(name) && !isContentDispositionSet) {
-            parseContentDisposition(value);
+            parseContentDisposition(field);
         } else if (MimeUtil.MIME_HEADER_LANGAUGE.equals(name) && !isContentLanguageSet) {
-            parseLanguage(value);
+            parseLanguage(field);
         } else if (MimeUtil.MIME_HEADER_LOCATION.equals(name) && !isContentLocationSet) {
-            parseLocation(value);
+            parseLocation(field);
         } else if (MimeUtil.MIME_HEADER_MD5.equals(name) && !isContentMD5Set) {
-            parseMD5(value);
+            parseMD5(field);
         } else {
             super.addField(field);
         }
     }
     
-    private void parseMD5(String value) {
+    private void parseMD5(final RawField field) {
+        String value = field.getBody();
         isContentMD5Set = true;
         if (value != null) {
             contentMD5Raw = value.trim();
         }
     }
 
-    private void parseLocation(final String value) {
+    private void parseLocation(final RawField field) {
         isContentLocationSet = true;
+        String value = field.getBody();
         if (value != null) {
             final StringReader stringReader = new StringReader(value);
             final StructuredFieldParser parser = new StructuredFieldParser(stringReader);
@@ -164,8 +169,9 @@ public class MaximalBodyDescriptor exten
         }
     }
     
-    private void parseLanguage(final String value) {
+    private void parseLanguage(final RawField field) {
         isContentLanguageSet = true;
+        String value = field.getBody();
         if (value != null) {
             try {
                 final ContentLanguageParser parser = new ContentLanguageParser(new StringReader(value));
@@ -176,10 +182,17 @@ public class MaximalBodyDescriptor exten
         }
     }
 
-    private void parseContentDisposition(final String value) throws MimeException {
+    private void parseContentDisposition(final RawField field) throws MimeException {
         isContentDispositionSet = true;
-        contentDispositionParameters = DefaultBodyDescriptor.getHeaderParams(value, getDecodeMonitor());
-        contentDispositionType = contentDispositionParameters.get("");
+        RawBody body = RawFieldParser.DEFAULT.parseRawBody(field);
+        Map<String, String> params = new HashMap<String, String>();
+        for (NameValuePair nmp: body.getParams()) {
+            String name = nmp.getName().toLowerCase(Locale.US);
+            params.put(name, nmp.getValue());
+        }
+        
+        contentDispositionType = body.getValue();
+        contentDispositionParameters = params;
         
         final String contentDispositionModificationDate 
             = contentDispositionParameters.get(MimeUtil.PARAM_MODIFICATION_DATE);
@@ -229,7 +242,8 @@ public class MaximalBodyDescriptor exten
         return result;
     }
     
-    private void parseContentDescription(String value) {
+    private void parseContentDescription(final RawField field) {
+        String value = field.getBody();
         if (value == null) {
             contentDescription = "";
         } else {
@@ -238,7 +252,8 @@ public class MaximalBodyDescriptor exten
         isContentDescriptionSet = true;
     }
 
-    private void parseContentId(final String value) {
+    private void parseContentId(final RawField field) {
+        String value = field.getBody();
         if (value == null) {
             contentId = "";
         } else {
@@ -247,8 +262,8 @@ public class MaximalBodyDescriptor exten
         isContentIdSet = true;
     }
 
-    private void parseMimeVersion(String value) {
-        final StringReader reader = new StringReader(value);
+    private void parseMimeVersion(RawField field) {
+        final StringReader reader = new StringReader(field.getBody());
         final MimeVersionParser parser = new MimeVersionParser(reader);
         try {
             parser.parse();



Mime
View raw message