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 eacd72f1cbafa8b8c0529cfeb5f8307d43091cad Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Nov 4 19:36:16 2026 +0100 [FIX] Strict parsing: stop() in a header or before a message/rfc822 body is not an error After stop(), the truncated buffer ends the header, and readRawField reported it as HEADERS_PREMATURE_END, which strict parsing turns into a MimeParseEventException. And at the end of the header of a message/rfc822 entity, the parser still created the encapsulated entity: created after stop(), it was not stopped and failed on its empty header. So a strict DefaultMessageBuilder#parseHeader failed on any message/rfc822 mail. A stopped entity no longer reports the end of its header as premature, and no longer recurses into an encapsulated message: its body is empty. --- .../org/apache/james/mime4j/stream/MimeEntity.java | 8 +++-- .../mime4j/stream/StrictMimeTokenStreamTest.java | 38 ++++++++++++++++++++++ .../DefaultMessageBuilderParseHeaderTest.java | 12 +++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java b/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java index ce311aea..d3c2426c 100644 --- a/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java +++ b/core/src/main/java/org/apache/james/mime4j/stream/MimeEntity.java @@ -236,7 +236,10 @@ class MimeEntity implements EntityStateMachine { } linebuf.clear(); if (instream.readLine(linebuf) == -1) { - monitor(Event.HEADERS_PREMATURE_END); + if (!stopped) { + // A header cut short by stop() is not a malformed message + monitor(Event.HEADERS_PREMATURE_END); + } endOfHeader = true; break; } @@ -331,7 +334,8 @@ class MimeEntity implements EntityStateMachine { state = EntityState.T_START_MULTIPART; clearMimePartStream(); } else if (recursionMode != RecursionMode.M_NO_RECURSE - && MimeUtil.isMessage(mimeType)) { + && MimeUtil.isMessage(mimeType) && !stopped) { + // Once stopped, the encapsulated message is not parsed: there is nothing left to read state = EntityState.T_BODY; return nextMessage(); } else { diff --git a/core/src/test/java/org/apache/james/mime4j/stream/StrictMimeTokenStreamTest.java b/core/src/test/java/org/apache/james/mime4j/stream/StrictMimeTokenStreamTest.java index b0c8ab68..f0f8c172 100644 --- a/core/src/test/java/org/apache/james/mime4j/stream/StrictMimeTokenStreamTest.java +++ b/core/src/test/java/org/apache/james/mime4j/stream/StrictMimeTokenStreamTest.java @@ -30,7 +30,9 @@ import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class StrictMimeTokenStreamTest { @@ -150,4 +152,40 @@ public class StrictMimeTokenStreamTest { private void checkNextIs(EntityState expected) throws Exception { Assert.assertEquals(MimeTokenStream.stateToString(expected), MimeTokenStream.stateToString(parser.next())); } + + @Test + public void stopOnAFieldShouldEndTheHeaderInStrictMode() throws Exception { + String msg = "Subject: a\r\nFrom: [email protected]\r\n\r\nbody\r\n"; + MimeTokenStream stream = new MimeTokenStream(MimeConfig.custom().setStrictParsing(true).build()); + stream.parse(new ByteArrayInputStream(msg.getBytes(StandardCharsets.US_ASCII))); + stream.next(); // T_START_HEADER + Assert.assertEquals(EntityState.T_FIELD, stream.next()); + stream.stop(); + + EntityState state; + do { + state = stream.next(); + } while (state != EntityState.T_END_OF_STREAM); + } + + @Test + public void stopAtTheEndOfAMessageRfc822HeaderShouldNotParseTheEncapsulatedMessage() throws Exception { + String msg = "Subject: a\r\nContent-Type: message/rfc822\r\n\r\n" + + "From: [email protected]\r\nSubject: c\r\n\r\nbody\r\n"; + MimeTokenStream stream = new MimeTokenStream(MimeConfig.custom().setStrictParsing(true).build()); + stream.parse(new ByteArrayInputStream(msg.getBytes(StandardCharsets.US_ASCII))); + while (stream.next() != EntityState.T_END_HEADER) { + // read the header + } + stream.stop(); + + List<EntityState> after = new ArrayList<>(); + EntityState state; + do { + state = stream.next(); + after.add(state); + } while (state != EntityState.T_END_OF_STREAM); + Assert.assertEquals(Arrays.asList(EntityState.T_BODY, EntityState.T_END_MESSAGE, + EntityState.T_END_OF_STREAM), after); + } } diff --git a/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageBuilderParseHeaderTest.java b/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageBuilderParseHeaderTest.java index a6098aed..95546819 100644 --- a/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageBuilderParseHeaderTest.java +++ b/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageBuilderParseHeaderTest.java @@ -127,4 +127,16 @@ public class DefaultMessageBuilderParseHeaderTest { } } } + + @Test + public void strictParseHeaderShouldReturnTheHeaderOfAMessageRfc822Mail() throws Exception { + String msg = "Subject: a\r\nContent-Type: message/rfc822\r\n\r\n" + + "From: [email protected]\r\nSubject: c\r\n\r\nbody\r\n"; + DefaultMessageBuilder builder = new DefaultMessageBuilder(); + builder.setMimeEntityConfig(MimeConfig.custom().setStrictParsing(true).build()); + + Header header = builder.parseHeader(new ByteArrayInputStream(msg.getBytes(StandardCharsets.US_ASCII))); + + assertThat(header.getFields()).extracting(f -> f.getName()).containsExactly("Subject", "Content-Type"); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
