MIME4J-27: Content length limitation Support
git-svn-id: https://svn.apache.org/repos/asf/james/mime4j/trunk@702290 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/james/mime4j/io/LimitedInputStream.java b/src/main/java/org/apache/james/mime4j/io/LimitedInputStream.java
new file mode 100644
index 0000000..020973b
--- /dev/null
+++ b/src/main/java/org/apache/james/mime4j/io/LimitedInputStream.java
@@ -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.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+public class LimitedInputStream extends PositionInputStream {
+
+ private final long limit;
+
+ public LimitedInputStream(InputStream instream, long limit) {
+ super(instream);
+ if (limit < 0) {
+ throw new IllegalArgumentException("Limit may not be negative");
+ }
+ this.limit = limit;
+ }
+
+ private void enforceLimit() throws IOException {
+ if (position >= limit) {
+ throw new IOException("Input stream limit exceeded");
+ }
+ }
+
+ public int read() throws IOException {
+ enforceLimit();
+ return super.read();
+ }
+
+ public int read(byte b[], int off, int len) throws IOException {
+ enforceLimit();
+ len = Math.min(len, getBytesLeft());
+ return super.read(b, off, len);
+ }
+
+ public long skip(long n) throws IOException {
+ enforceLimit();
+ n = Math.min(n, getBytesLeft());
+ return super.skip(n);
+ }
+
+ private int getBytesLeft() {
+ return (int)Math.min(Integer.MAX_VALUE, limit - position);
+ }
+
+}
diff --git a/src/main/java/org/apache/james/mime4j/io/PartialInputStream.java b/src/main/java/org/apache/james/mime4j/io/PartialInputStream.java
index 9af4037..fe6bc60 100644
--- a/src/main/java/org/apache/james/mime4j/io/PartialInputStream.java
+++ b/src/main/java/org/apache/james/mime4j/io/PartialInputStream.java
@@ -45,12 +45,12 @@
public int read(byte b[], int off, int len) throws IOException {
len = Math.min(len, getBytesLeft());
- return super.read(b, off, len); //To change body of overridden methods use File | Settings | File Templates.
+ return super.read(b, off, len);
}
public long skip(long n) throws IOException {
n = Math.min(n, getBytesLeft());
- return super.skip(n); //To change body of overridden methods use File | Settings | File Templates.
+ return super.skip(n);
}
private int getBytesLeft() {
diff --git a/src/main/java/org/apache/james/mime4j/io/PositionInputStream.java b/src/main/java/org/apache/james/mime4j/io/PositionInputStream.java
index 912742f..f858393 100644
--- a/src/main/java/org/apache/james/mime4j/io/PositionInputStream.java
+++ b/src/main/java/org/apache/james/mime4j/io/PositionInputStream.java
@@ -68,13 +68,15 @@
public long skip(long n) throws IOException {
final long c = in.skip(n);
- position += c;
+ if (c > 0)
+ position += c;
return c;
}
public int read(byte b[], int off, int len) throws IOException {
final int c = in.read(b, off, len);
- position += c;
+ if (c > 0)
+ position += c;
return c;
}
diff --git a/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java b/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java
index 798f2d6..e29dff0 100644
--- a/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java
+++ b/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java
@@ -27,6 +27,7 @@
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import org.apache.james.mime4j.descriptor.BodyDescriptor;
import org.apache.james.mime4j.io.BufferedLineReaderInputStream;
+import org.apache.james.mime4j.io.LimitedInputStream;
import org.apache.james.mime4j.io.LineReaderInputStream;
import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor;
import org.apache.james.mime4j.io.MimeBoundaryInputStream;
@@ -228,7 +229,8 @@
if (tmpbuf == null) {
tmpbuf = new byte[2048];
}
- while (dataStream.read(tmpbuf)!= -1) {
+ InputStream instream = getLimitedContentStream();
+ while (instream.read(tmpbuf)!= -1) {
}
}
}
@@ -286,13 +288,22 @@
}
}
+ private InputStream getLimitedContentStream() {
+ long maxContentLimit = config.getMaxContentLen();
+ if (maxContentLimit >= 0) {
+ return new LimitedInputStream(dataStream, maxContentLimit);
+ } else {
+ return dataStream;
+ }
+ }
+
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;
+ return getLimitedContentStream();
default:
throw new IllegalStateException("Invalid state: " + stateToString(state));
}
diff --git a/src/main/java/org/apache/james/mime4j/parser/MimeEntityConfig.java b/src/main/java/org/apache/james/mime4j/parser/MimeEntityConfig.java
index 13bf469..7e55453 100644
--- a/src/main/java/org/apache/james/mime4j/parser/MimeEntityConfig.java
+++ b/src/main/java/org/apache/james/mime4j/parser/MimeEntityConfig.java
@@ -31,6 +31,7 @@
private boolean strictParsing;
private int maxLineLen;
private int maxHeaderCount;
+ private long maxContentLen;
public MimeEntityConfig() {
super();
@@ -38,6 +39,7 @@
this.strictParsing = false;
this.maxLineLen = 1000;
this.maxHeaderCount = 1000;
+ this.maxContentLen = -1;
}
public boolean isMaximalBodyDescriptor() {
@@ -115,6 +117,28 @@
return this.maxHeaderCount;
}
+ /**
+ * Sets the maximum line length limit. Parsing of a MIME entity will be terminated
+ * with a {@link MimeException} if a content body exceeds the maximum length limit.
+ * If this parameter is set to a non positive value the content length
+ * check will be disabled.
+ *
+ * @param maxLineLen maximum line length limit
+ */
+ public void setMaxContentLen(long maxContentLen) {
+ this.maxContentLen = maxContentLen;
+ }
+
+ /**
+ * Returns the maximum content length limit
+ * @see #setMaxContentLen(long)
+ *
+ * @return value of the the maximum content length limit
+ */
+ public long getMaxContentLen() {
+ return maxContentLen;
+ }
+
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
diff --git a/src/test/java/org/apache/james/mime4j/io/LimitedInputStreamTest.java b/src/test/java/org/apache/james/mime4j/io/LimitedInputStreamTest.java
new file mode 100644
index 0000000..6a79822
--- /dev/null
+++ b/src/test/java/org/apache/james/mime4j/io/LimitedInputStreamTest.java
@@ -0,0 +1,56 @@
+/****************************************************************
+ * 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.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+public class LimitedInputStreamTest extends TestCase {
+
+ public void testUpToLimitRead() throws IOException {
+ byte[] data = new byte[] {'0', '1', '2', '3', '4', '5', '6'};
+ ByteArrayInputStream instream = new ByteArrayInputStream(data);
+ LimitedInputStream limitedStream = new LimitedInputStream(instream, 3);
+ assertEquals(0, limitedStream.getPosition());
+ assertTrue(limitedStream.read() != -1);
+ assertEquals(1, limitedStream.getPosition());
+ byte[] tmp = new byte[3];
+ assertEquals(2, limitedStream.read(tmp));
+ assertEquals(3, limitedStream.getPosition());
+ try {
+ limitedStream.read();
+ fail("IOException should have been thrown");
+ } catch (IOException ex) {
+ }
+ try {
+ limitedStream.read(tmp);
+ fail("IOException should have been thrown");
+ } catch (IOException ex) {
+ }
+ try {
+ limitedStream.skip(2);
+ fail("IOException should have been thrown");
+ } catch (IOException ex) {
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/james/mime4j/io/PositionInputStreamTest.java b/src/test/java/org/apache/james/mime4j/io/PositionInputStreamTest.java
new file mode 100644
index 0000000..62c5495
--- /dev/null
+++ b/src/test/java/org/apache/james/mime4j/io/PositionInputStreamTest.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+public class PositionInputStreamTest extends TestCase {
+
+ public void testPositionCounting() throws IOException {
+ byte[] data = new byte[] {'0', '1', '2', '3', '4', '5', '6'};
+ ByteArrayInputStream instream = new ByteArrayInputStream(data);
+ PositionInputStream countingStream = new PositionInputStream(instream);
+ assertEquals(0, countingStream.getPosition());
+ assertTrue(countingStream.read() != -1);
+ assertEquals(1, countingStream.getPosition());
+ byte[] tmp = new byte[3];
+ assertEquals(3, countingStream.read(tmp));
+ assertEquals(4, countingStream.getPosition());
+ assertEquals(2, countingStream.skip(2));
+ assertEquals(6, countingStream.getPosition());
+ assertTrue(countingStream.read() != -1);
+ assertEquals(7, countingStream.getPosition());
+ assertTrue(countingStream.read() == -1);
+ assertEquals(7, countingStream.getPosition());
+ assertTrue(countingStream.read() == -1);
+ assertEquals(7, countingStream.getPosition());
+ assertTrue(countingStream.read(tmp) == -1);
+ assertEquals(7, countingStream.getPosition());
+ assertTrue(countingStream.read(tmp) == -1);
+ assertEquals(7, countingStream.getPosition());
+ }
+
+}
diff --git a/src/test/java/org/apache/james/mime4j/parser/MimeEntityTest.java b/src/test/java/org/apache/james/mime4j/parser/MimeEntityTest.java
index c28bfac..00609a1 100644
--- a/src/test/java/org/apache/james/mime4j/parser/MimeEntityTest.java
+++ b/src/test/java/org/apache/james/mime4j/parser/MimeEntityTest.java
@@ -20,6 +20,7 @@
package org.apache.james.mime4j.parser;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.io.BufferedLineReaderInputStream;
@@ -483,4 +484,61 @@
}
}
+ public void testMaxContentLimitCheck() throws Exception {
+ String message =
+ "To: Road Runner <runner@example.org>\r\n" +
+ "From: Wile E. Cayote <wile@example.org>\r\n" +
+ "Date: Tue, 12 Feb 2008 17:34:09 +0000 (GMT)\r\n" +
+ "Subject: Mail\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n" +
+ "DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS DoS\r\n";
+ byte[] raw = message.getBytes("US-ASCII");
+ ByteArrayInputStream instream = new ByteArrayInputStream(raw);
+ RootInputStream rootStream = new RootInputStream(instream);
+ BufferedLineReaderInputStream rawstream = new BufferedLineReaderInputStream(rootStream, 12);
+
+ MimeEntityConfig config = new MimeEntityConfig();
+ config.setMaxContentLen(100);
+ MimeEntity entity = new MimeEntity(
+ rootStream,
+ rawstream,
+ null,
+ EntityStates.T_START_MESSAGE,
+ EntityStates.T_END_MESSAGE,
+ config);
+
+ assertEquals(EntityStates.T_START_MESSAGE, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_START_HEADER, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_FIELD, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_FIELD, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_FIELD, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_FIELD, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_FIELD, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_END_HEADER, entity.getState());
+ entity.advance();
+ assertEquals(EntityStates.T_BODY, entity.getState());
+ try {
+ IOUtils.toByteArray(entity.getContentStream());
+ fail("IOException should have been thrown");
+ } catch (IOException expected) {
+ }
+ }
+
}