This is an automated email from the ASF dual-hosted git repository. chibenwa pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-mime4j.git
commit d9d2f89ee836fd4831763f5e7403452b6352a1f7 Author: Benoit TELLIER <[email protected]> AuthorDate: Mon Aug 31 15:34:05 2026 +0200 [FIX] Limit multipart count and nesting --- .../mime4j/io/MaxNestingDepthLimitException.java | 38 ++++ .../mime4j/io/MaxPartCountLimitException.java | 38 ++++ .../org/apache/james/mime4j/stream/MimeConfig.java | 85 +++++++ .../james/mime4j/stream/MimeTokenStream.java | 30 +++ .../james/mime4j/stream/MimeEntityLimitsTest.java | 253 +++++++++++++++++++++ .../james/mime4j/dom/MessagePartLimitsTest.java | 99 ++++++++ 6 files changed, 543 insertions(+) diff --git a/core/src/main/java/org/apache/james/mime4j/io/MaxNestingDepthLimitException.java b/core/src/main/java/org/apache/james/mime4j/io/MaxNestingDepthLimitException.java new file mode 100644 index 00000000..399ed758 --- /dev/null +++ b/core/src/main/java/org/apache/james/mime4j/io/MaxNestingDepthLimitException.java @@ -0,0 +1,38 @@ +/**************************************************************** + * 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 org.apache.james.mime4j.MimeException; + +/** + * Signals a parsing error due to MIME entities (multiparts and embedded + * messages) being nested more deeply than the maximum limit. + * + * @see org.apache.james.mime4j.stream.MimeConfig.Builder#setMaxNestingDepth(int) + */ +public class MaxNestingDepthLimitException extends MimeException { + + private static final long serialVersionUID = 6011628067570972836L; + + public MaxNestingDepthLimitException(final String message) { + super(message); + } + +} diff --git a/core/src/main/java/org/apache/james/mime4j/io/MaxPartCountLimitException.java b/core/src/main/java/org/apache/james/mime4j/io/MaxPartCountLimitException.java new file mode 100644 index 00000000..e26bcacd --- /dev/null +++ b/core/src/main/java/org/apache/james/mime4j/io/MaxPartCountLimitException.java @@ -0,0 +1,38 @@ +/**************************************************************** + * 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 org.apache.james.mime4j.MimeException; + +/** + * Signals a parsing error due to the total number of MIME entities (body parts + * and embedded messages) contained in a message exceeding the maximum limit. + * + * @see org.apache.james.mime4j.stream.MimeConfig.Builder#setMaxPartCount(int) + */ +public class MaxPartCountLimitException extends MimeException { + + private static final long serialVersionUID = -7742064603843344870L; + + public MaxPartCountLimitException(final String message) { + super(message); + } + +} diff --git a/core/src/main/java/org/apache/james/mime4j/stream/MimeConfig.java b/core/src/main/java/org/apache/james/mime4j/stream/MimeConfig.java index 77ad1527..c11ee6f7 100644 --- a/core/src/main/java/org/apache/james/mime4j/stream/MimeConfig.java +++ b/core/src/main/java/org/apache/james/mime4j/stream/MimeConfig.java @@ -43,6 +43,8 @@ public final class MimeConfig { private final int maxHeaderCount; private final int maxHeaderLen; private final long maxContentLen; + private final int maxPartCount; + private final int maxNestingDepth; private final boolean countLineNumbers; private final String headlessParsing; private final boolean malformedHeaderStartsBody; @@ -53,6 +55,8 @@ public final class MimeConfig { int maxHeaderCount, int maxHeaderLen, long maxContentLen, + int maxPartCount, + int maxNestingDepth, boolean countLineNumbers, String headlessParsing, boolean malformedHeaderStartsBody) { @@ -63,6 +67,8 @@ public final class MimeConfig { this.maxHeaderCount = maxHeaderCount; this.maxHeaderLen = maxHeaderLen; this.maxContentLen = maxContentLen; + this.maxPartCount = maxPartCount; + this.maxNestingDepth = maxNestingDepth; this.headlessParsing = headlessParsing; } @@ -131,6 +137,28 @@ public final class MimeConfig { return maxContentLen; } + /** + * Returns the maximum part count limit + * + * @see Builder#setMaxPartCount(int) + * + * @return value of the maximum part count limit + */ + public int getMaxPartCount() { + return maxPartCount; + } + + /** + * Returns the maximum nesting depth limit + * + * @see Builder#setMaxNestingDepth(int) + * + * @return value of the maximum nesting depth limit + */ + public int getMaxNestingDepth() { + return maxNestingDepth; + } + /** * Returns the value of the line number counting mode. * @@ -159,6 +187,8 @@ public final class MimeConfig { .append(", maxHeaderCount=").append(maxHeaderCount) .append(", maxHeaderLen=").append(maxHeaderLen) .append(", maxContentLen=").append(maxContentLen) + .append(", maxPartCount=").append(maxPartCount) + .append(", maxNestingDepth=").append(maxNestingDepth) .append(", countLineNumbers=").append(countLineNumbers) .append(", headlessParsing=").append(headlessParsing) .append(", malformedHeaderStartsBody=").append(malformedHeaderStartsBody) @@ -180,6 +210,8 @@ public final class MimeConfig { .setMaxHeaderCount(config.getMaxHeaderCount()) .setMaxHeaderLen(config.getMaxHeaderLen()) .setMaxContentLen(config.getMaxContentLen()) + .setMaxPartCount(config.getMaxPartCount()) + .setMaxNestingDepth(config.getMaxNestingDepth()) .setCountLineNumbers(config.isCountLineNumbers()) .setHeadlessParsing(config.getHeadlessParsing()) .setMalformedHeaderStartsBody(config.isMalformedHeaderStartsBody()); @@ -192,6 +224,8 @@ public final class MimeConfig { private int maxHeaderCount; private int maxHeaderLen; private long maxContentLen; + private int maxPartCount; + private int maxNestingDepth; private boolean countLineNumbers; private String headlessParsing; private boolean malformedHeaderStartsBody; @@ -204,6 +238,8 @@ public final class MimeConfig { this.maxHeaderCount = 1000; this.maxHeaderLen = 10000; this.maxContentLen = -1; + this.maxPartCount = 512; + this.maxNestingDepth = 64; this.headlessParsing = null; } @@ -309,6 +345,53 @@ public final class MimeConfig { return this; } + /** + * Sets the maximum number of MIME entities (body parts and embedded + * messages) a message may be made of. Parsing will be terminated with a + * {@link org.apache.james.mime4j.io.MaxPartCountLimitException} if a + * message contains more entities than this limit. If this parameter is + * set to a non positive value the part count check will be disabled. + * <p> + * The top level message itself is not counted, so a limit of + * <code>1</code> allows a multipart message holding a single body part. + * <p> + * Unlike {@link #setMaxHeaderCount(int)} and + * {@link #setMaxContentLen(long)}, which are enforced per entity, this + * limit applies to the message as a whole. Without it a small message + * made of a very large number of tiny parts can make a consumer + * building an object graph per part exhaust its memory. + * <p> + * Default value: <code>512</code> + * + * @param maxPartCount + * maximum part count limit + */ + public Builder setMaxPartCount(int maxPartCount) { + this.maxPartCount = maxPartCount; + return this; + } + + /** + * Sets the maximum nesting depth of MIME entities. Parsing will be + * terminated with a + * {@link org.apache.james.mime4j.io.MaxNestingDepthLimitException} if + * entities are nested more deeply than this limit. If this parameter is + * set to a non positive value the nesting depth check will be disabled. + * <p> + * The top level message is at depth <code>1</code>, so a limit of + * <code>2</code> allows a multipart message whose body parts are not + * themselves multipart or embedded messages. + * <p> + * Default value: <code>64</code> + * + * @param maxNestingDepth + * maximum nesting depth limit + */ + public Builder setMaxNestingDepth(int maxNestingDepth) { + this.maxNestingDepth = maxNestingDepth; + return this; + } + /** * Defines whether the parser should count line numbers. If enabled line * numbers are included in the debug output. @@ -346,6 +429,8 @@ public final class MimeConfig { maxHeaderCount, maxHeaderLen, maxContentLen, + maxPartCount, + maxNestingDepth, countLineNumbers, headlessParsing, malformedHeaderStartsBody); diff --git a/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java b/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java index 8b6bcd7c..4fba9d0b 100644 --- a/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java +++ b/core/src/main/java/org/apache/james/mime4j/stream/MimeTokenStream.java @@ -31,6 +31,8 @@ import org.apache.james.mime4j.Charsets; import org.apache.james.mime4j.MimeException; import org.apache.james.mime4j.codec.DecodeMonitor; import org.apache.james.mime4j.io.LineNumberInputStream; +import org.apache.james.mime4j.io.MaxNestingDepthLimitException; +import org.apache.james.mime4j.io.MaxPartCountLimitException; import org.apache.james.mime4j.util.CharsetUtil; /** @@ -88,6 +90,7 @@ public class MimeTokenStream { private EntityStateMachine currentStateMachine; private RecursionMode recursionMode = RecursionMode.M_RECURSE; private MimeEntity rootentity; + private int partCount; /** * Constructs a standard (lax) stream. @@ -204,6 +207,7 @@ public class MimeTokenStream { rootentity.setRecursionMode(recursionMode); currentStateMachine = rootentity; + partCount = 0; entities.clear(); entities.add(currentStateMachine); state = currentStateMachine.getState(); @@ -374,6 +378,7 @@ public class MimeTokenStream { while (currentStateMachine != null) { EntityStateMachine next = currentStateMachine.advance(); if (next != null) { + checkEntityLimits(); entities.add(next); currentStateMachine = next; } @@ -396,6 +401,31 @@ public class MimeTokenStream { return state; } + /** + * Enforces the limits on the total number of MIME entities a message may be + * made of and on how deeply those entities may be nested. Both are message + * wide limits, unlike the header and content limits which are enforced per + * entity, and they bound the work a consumer of this token stream can be + * made to do by a small but pathologically structured message. + * <p> + * Called before a newly discovered entity is pushed onto the stack. + */ + private void checkEntityLimits() throws MimeException { + int maxPartCount = config.getMaxPartCount(); + if (maxPartCount > 0 && partCount >= maxPartCount) { + throw new MaxPartCountLimitException("Maximum part count limit (" + + maxPartCount + ") exceeded"); + } + partCount++; + int maxNestingDepth = config.getMaxNestingDepth(); + // entities already holds the parent chain of the entity being pushed, + // the root message included, so the new entity sits at depth size + 1. + if (maxNestingDepth > 0 && entities.size() >= maxNestingDepth) { + throw new MaxNestingDepthLimitException("Maximum nesting depth limit (" + + maxNestingDepth + ") exceeded"); + } + } + /** * Renders a state as a string suitable for logging. * @param state diff --git a/core/src/test/java/org/apache/james/mime4j/stream/MimeEntityLimitsTest.java b/core/src/test/java/org/apache/james/mime4j/stream/MimeEntityLimitsTest.java new file mode 100644 index 00000000..6305e1a6 --- /dev/null +++ b/core/src/test/java/org/apache/james/mime4j/stream/MimeEntityLimitsTest.java @@ -0,0 +1,253 @@ +/**************************************************************** + * 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.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.james.mime4j.Charsets; +import org.apache.james.mime4j.io.MaxNestingDepthLimitException; +import org.apache.james.mime4j.io.MaxPartCountLimitException; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests the message wide limits bounding the number of MIME entities a message + * may be made of and how deeply they may be nested. + */ +public class MimeEntityLimitsTest { + + /** + * A multipart message made of <code>count</code> empty body parts. Each part + * costs about 10 bytes on the wire but makes a consumer building an object + * graph per part allocate far more than that. + */ + private static InputStream flatMultipart(int count) { + StringBuilder sb = new StringBuilder(); + sb.append("Content-Type: multipart/mixed; boundary=b\r\n\r\n"); + for (int i = 0; i < count; i++) { + sb.append("--b\r\n\r\n"); + } + sb.append("--b--\r\n"); + return new ByteArrayInputStream(sb.toString().getBytes(Charsets.US_ASCII)); + } + + /** + * A message of nested multiparts, <code>depth</code> levels deep. The top + * level message is at depth 1, so the innermost body part is at depth + * <code>depth</code>. + */ + private static InputStream nestedMultipart(int depth) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < depth; i++) { + sb.append("Content-Type: multipart/mixed; boundary=b").append(i).append("\r\n\r\n"); + sb.append("--b").append(i).append("\r\n"); + } + sb.append("\r\n"); + for (int i = depth - 1; i >= 1; i--) { + sb.append("--b").append(i).append("--\r\n"); + } + return new ByteArrayInputStream(sb.toString().getBytes(Charsets.US_ASCII)); + } + + private static int parse(MimeConfig config, InputStream in) throws Exception { + MimeTokenStream stream = new MimeTokenStream(config); + stream.parse(in); + int parts = 0; + for (EntityState state = stream.getState(); + state != EntityState.T_END_OF_STREAM; + state = stream.next()) { + if (state == EntityState.T_START_BODYPART) { + parts++; + } + } + return parts; + } + + @Test + public void partCountLimitShouldAcceptAMessageAtTheLimit() throws Exception { + MimeConfig config = MimeConfig.custom().setMaxPartCount(3).build(); + Assert.assertEquals(3, parse(config, flatMultipart(3))); + } + + @Test + public void partCountLimitShouldRejectAMessageOverTheLimit() throws Exception { + MimeConfig config = MimeConfig.custom().setMaxPartCount(3).build(); + try { + parse(config, flatMultipart(4)); + Assert.fail("MaxPartCountLimitException expected"); + } catch (MaxPartCountLimitException expected) { + Assert.assertEquals("Maximum part count limit (3) exceeded", expected.getMessage()); + } + } + + @Test + public void partCountLimitShouldBeDisabledWhenNegative() throws Exception { + MimeConfig config = MimeConfig.custom().setMaxPartCount(-1).build(); + Assert.assertEquals(2000, parse(config, flatMultipart(2000))); + } + + @Test + public void partCountLimitShouldBeDisabledWhenZero() throws Exception { + MimeConfig config = MimeConfig.custom().setMaxPartCount(0).build(); + Assert.assertEquals(2000, parse(config, flatMultipart(2000))); + } + + @Test + public void partCountLimitShouldBeDisabledOnACopyOfPermissive() throws Exception { + MimeConfig config = MimeConfig.copy(MimeConfig.PERMISSIVE).setMaxPartCount(-1).build(); + Assert.assertEquals(-1, config.getMaxPartCount()); + Assert.assertEquals(2000, parse(config, flatMultipart(2000))); + } + + @Test + public void partCountLimitShouldCountEmbeddedMessages() throws Exception { + // multipart/mixed holding one message/rfc822: the body part and the + // embedded message are two entities. + String message = "Content-Type: multipart/mixed; boundary=b\r\n\r\n" + + "--b\r\n" + + "Content-Type: message/rfc822\r\n\r\n" + + "Subject: embedded\r\n\r\n" + + "body\r\n" + + "--b--\r\n"; + InputStream in = new ByteArrayInputStream(message.getBytes(Charsets.US_ASCII)); + try { + parse(MimeConfig.custom().setMaxPartCount(1).build(), in); + Assert.fail("MaxPartCountLimitException expected"); + } catch (MaxPartCountLimitException expected) { + // expected + } + } + + @Test + public void defaultConfigShouldAcceptAMessageWithinThePartCountDefault() throws Exception { + Assert.assertEquals(512, parse(MimeConfig.DEFAULT, flatMultipart(512))); + } + + @Test + public void defaultConfigShouldBoundThePartCount() throws Exception { + try { + parse(MimeConfig.DEFAULT, flatMultipart(513)); + Assert.fail("MaxPartCountLimitException expected"); + } catch (MaxPartCountLimitException expected) { + // expected + } + } + + @Test + public void permissiveConfigShouldBoundThePartCount() throws Exception { + try { + parse(MimeConfig.PERMISSIVE, flatMultipart(513)); + Assert.fail("MaxPartCountLimitException expected"); + } catch (MaxPartCountLimitException expected) { + // expected + } + } + + @Test + public void nestingDepthLimitShouldAcceptAMessageAtTheLimit() throws Exception { + MimeConfig config = MimeConfig.custom() + .setMaxNestingDepth(4) + .setMaxPartCount(-1) + .build(); + Assert.assertEquals(3, parse(config, nestedMultipart(4))); + } + + @Test + public void nestingDepthLimitShouldRejectAMessageOverTheLimit() throws Exception { + MimeConfig config = MimeConfig.custom() + .setMaxNestingDepth(4) + .setMaxPartCount(-1) + .build(); + try { + parse(config, nestedMultipart(5)); + Assert.fail("MaxNestingDepthLimitException expected"); + } catch (MaxNestingDepthLimitException expected) { + Assert.assertEquals("Maximum nesting depth limit (4) exceeded", expected.getMessage()); + } + } + + @Test + public void nestingDepthLimitShouldBeDisabledWhenNegative() throws Exception { + MimeConfig config = MimeConfig.custom() + .setMaxNestingDepth(-1) + .setMaxPartCount(-1) + .build(); + Assert.assertEquals(199, parse(config, nestedMultipart(200))); + } + + @Test + public void nestingDepthLimitShouldBeDisabledWhenZero() throws Exception { + MimeConfig config = MimeConfig.custom() + .setMaxNestingDepth(0) + .setMaxPartCount(0) + .build(); + Assert.assertEquals(199, parse(config, nestedMultipart(200))); + } + + @Test + public void nestingDepthLimitShouldBeDisabledOnACopyOfPermissive() throws Exception { + MimeConfig config = MimeConfig.copy(MimeConfig.PERMISSIVE) + .setMaxNestingDepth(-1) + .setMaxPartCount(-1) + .build(); + Assert.assertEquals(-1, config.getMaxNestingDepth()); + Assert.assertEquals(199, parse(config, nestedMultipart(200))); + } + + @Test + public void defaultConfigShouldAcceptAMessageWithinTheNestingDepthDefault() throws Exception { + MimeConfig config = MimeConfig.copy(MimeConfig.DEFAULT).setMaxPartCount(-1).build(); + Assert.assertEquals(63, parse(config, nestedMultipart(64))); + } + + @Test + public void defaultConfigShouldBoundTheNestingDepth() throws Exception { + MimeConfig config = MimeConfig.copy(MimeConfig.DEFAULT).setMaxPartCount(-1).build(); + try { + parse(config, nestedMultipart(65)); + Assert.fail("MaxNestingDepthLimitException expected"); + } catch (MaxNestingDepthLimitException expected) { + // expected + } + } + + @Test + public void limitsShouldBeResetBetweenParses() throws Exception { + MimeConfig config = MimeConfig.custom().setMaxPartCount(3).build(); + MimeTokenStream stream = new MimeTokenStream(config); + for (int i = 0; i < 3; i++) { + stream.parse(flatMultipart(3)); + while (stream.getState() != EntityState.T_END_OF_STREAM) { + stream.next(); + } + } + } + + @Test + public void copyShouldCarryTheLimitsOver() { + MimeConfig config = MimeConfig.copy(MimeConfig.custom() + .setMaxPartCount(7) + .setMaxNestingDepth(9) + .build()).build(); + Assert.assertEquals(7, config.getMaxPartCount()); + Assert.assertEquals(9, config.getMaxNestingDepth()); + } +} diff --git a/dom/src/test/java/org/apache/james/mime4j/dom/MessagePartLimitsTest.java b/dom/src/test/java/org/apache/james/mime4j/dom/MessagePartLimitsTest.java new file mode 100644 index 00000000..954fefb7 --- /dev/null +++ b/dom/src/test/java/org/apache/james/mime4j/dom/MessagePartLimitsTest.java @@ -0,0 +1,99 @@ +/**************************************************************** + * 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.dom; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.james.mime4j.Charsets; +import org.apache.james.mime4j.MimeIOException; +import org.apache.james.mime4j.io.MaxNestingDepthLimitException; +import org.apache.james.mime4j.io.MaxPartCountLimitException; +import org.apache.james.mime4j.message.DefaultMessageBuilder; +import org.apache.james.mime4j.stream.MimeConfig; +import org.junit.Assert; +import org.junit.Test; + +/** + * Building a DOM allocates an object graph per MIME entity, so a small message + * made of a huge number of tiny parts is an amplification vector. These tests + * pin down that the message wide entity limits bound that work, including under + * {@link MimeConfig#PERMISSIVE} which is what mail servers typically use on + * untrusted inbound traffic. + */ +public class MessagePartLimitsTest { + + private static InputStream flatMultipart(int count) { + StringBuilder sb = new StringBuilder(); + sb.append("Subject: hi\r\n"); + sb.append("Content-Type: multipart/mixed; boundary=b\r\n\r\n"); + for (int i = 0; i < count; i++) { + sb.append("--b\r\n\r\n"); + } + sb.append("--b--\r\n"); + return new ByteArrayInputStream(sb.toString().getBytes(Charsets.US_ASCII)); + } + + private static InputStream nestedMultipart(int depth) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < depth; i++) { + sb.append("Content-Type: multipart/mixed; boundary=b").append(i).append("\r\n\r\n"); + sb.append("--b").append(i).append("\r\n"); + } + sb.append("\r\n"); + for (int i = depth - 1; i >= 1; i--) { + sb.append("--b").append(i).append("--\r\n"); + } + return new ByteArrayInputStream(sb.toString().getBytes(Charsets.US_ASCII)); + } + + private static DefaultMessageBuilder builder(MimeConfig config) { + DefaultMessageBuilder builder = new DefaultMessageBuilder(); + builder.setMimeEntityConfig(config); + return builder; + } + + @Test + public void parseMessageShouldRejectTooManyPartsWithPermissiveConfig() throws Exception { + try { + builder(MimeConfig.PERMISSIVE).parseMessage(flatMultipart(50000)); + Assert.fail("MimeIOException expected"); + } catch (MimeIOException e) { + Assert.assertTrue(e.getCause() instanceof MaxPartCountLimitException); + } + } + + @Test + public void parseMessageShouldRejectTooDeeplyNestedPartsWithPermissiveConfig() throws Exception { + MimeConfig config = MimeConfig.copy(MimeConfig.PERMISSIVE).setMaxPartCount(-1).build(); + try { + builder(config).parseMessage(nestedMultipart(5000)); + Assert.fail("MimeIOException expected"); + } catch (MimeIOException e) { + Assert.assertTrue(e.getCause() instanceof MaxNestingDepthLimitException); + } + } + + @Test + public void parseMessageShouldAcceptAMessageWithinTheLimits() throws Exception { + Message message = builder(MimeConfig.PERMISSIVE).parseMessage(flatMultipart(512)); + Assert.assertEquals(512, ((Multipart) message.getBody()).getCount()); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
