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();