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 fbfbf0e2d6d41cf32203a72dc8fd76ebcf46a944 Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Nov 4 15:10:07 2026 +0100 [FIX] FallbackBodyDescriptorBuilder: take the charset of the first Content-Type when none is usable FallbackBodyDescriptorBuilder parses every Content-Type field until one gives a usable type, and each one replaced the charset, so with no usable field the charset came from the last one. DefaultBodyDescriptorBuilder keeps the first field in that case, and core and DOM decoded the body with different charsets. A later field now replaces the charset only when its type is accepted, as DefaultBodyDescriptorBuilder replaces the field. Fixes #107 --- .../stream/FallbackBodyDescriptorBuilder.java | 19 +++++++++---- .../mime4j/message/MaximalBodyDescriptorTest.java | 33 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/apache/james/mime4j/stream/FallbackBodyDescriptorBuilder.java b/core/src/main/java/org/apache/james/mime4j/stream/FallbackBodyDescriptorBuilder.java index e06bf68d..af6529f6 100644 --- a/core/src/main/java/org/apache/james/mime4j/stream/FallbackBodyDescriptorBuilder.java +++ b/core/src/main/java/org/apache/james/mime4j/stream/FallbackBodyDescriptorBuilder.java @@ -53,6 +53,7 @@ class FallbackBodyDescriptorBuilder implements BodyDescriptorBuilder { private String charset; private String transferEncoding; private long contentLength; + private boolean contentTypeSeen; /** * Creates a new root <code>BodyDescriptor</code> instance. @@ -80,6 +81,7 @@ class FallbackBodyDescriptorBuilder implements BodyDescriptorBuilder { charset = null; transferEncoding = null; contentLength = -1; + contentTypeSeen = false; } public BodyDescriptorBuilder newChild() { @@ -197,14 +199,19 @@ class FallbackBodyDescriptorBuilder implements BodyDescriptorBuilder { boundary = b; } - String c = params.get("charset"); - charset = null; - if (c != null) { - c = c.trim(); - if (c.length() > 0) { - charset = c; + // When no field gives a usable type, the charset comes from the first one, + // as for DefaultBodyDescriptorBuilder + if (mimeType != null || !contentTypeSeen) { + String c = params.get("charset"); + charset = null; + if (c != null) { + c = c.trim(); + if (c.length() > 0) { + charset = c; + } } } + contentTypeSeen = true; } } diff --git a/dom/src/test/java/org/apache/james/mime4j/message/MaximalBodyDescriptorTest.java b/dom/src/test/java/org/apache/james/mime4j/message/MaximalBodyDescriptorTest.java index 7591b05f..0af16a26 100644 --- a/dom/src/test/java/org/apache/james/mime4j/message/MaximalBodyDescriptorTest.java +++ b/dom/src/test/java/org/apache/james/mime4j/message/MaximalBodyDescriptorTest.java @@ -20,6 +20,8 @@ package org.apache.james.mime4j.message; import org.apache.james.mime4j.ExampleMail; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.field.LenientFieldParser; import org.apache.james.mime4j.stream.BodyDescriptor; import org.apache.james.mime4j.stream.BodyDescriptorBuilder; import org.apache.james.mime4j.stream.EntityState; @@ -34,7 +36,9 @@ import org.junit.Before; import org.junit.Test; import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; +import java.util.Locale; import java.util.TimeZone; public class MaximalBodyDescriptorTest { @@ -336,6 +340,35 @@ public class MaximalBodyDescriptorTest { return (MaximalBodyDescriptor) descriptor; } + @Test + public void unusableContentTypesShouldGiveTheSameCharsetInCoreAndDom() throws Exception { + String[] headers = { + "Content-Type: foo; charset=utf-8\r\nContent-Type: bar\r\n", + "Content-Type: foo\r\nContent-Type: bar; charset=utf-8\r\n", + "Content-Type: foo; charset=utf-8\r\nContent-Type: text/html\r\n", + "Content-Type: multipart/mixed; charset=utf-8\r\nContent-Type: bar; charset=iso-8859-1\r\n"}; + for (String header : headers) { + byte[] msg = (header + "\r\nbody\r\n").getBytes(StandardCharsets.US_ASCII); + + MimeTokenStream core = new MimeTokenStream(); // FallbackBodyDescriptorBuilder + core.parse(new ByteArrayInputStream(msg)); + MimeTokenStream dom = new MimeTokenStream(MimeConfig.DEFAULT, null, + new DefaultBodyDescriptorBuilder(null, LenientFieldParser.getParser(), DecodeMonitor.SILENT)); + dom.parse(new ByteArrayInputStream(msg)); + while (core.next() != EntityState.T_BODY) { } + while (dom.next() != EntityState.T_BODY) { } + + Assert.assertEquals(header, dom.getBodyDescriptor().getMimeType(), core.getBodyDescriptor().getMimeType()); + // the default charset is "US-ASCII" in DOM and "us-ascii" in core + Assert.assertEquals(header, lowerCase(dom.getBodyDescriptor().getCharset()), + lowerCase(core.getBodyDescriptor().getCharset())); + } + } + + private static String lowerCase(String s) { + return s == null ? null : s.toLowerCase(Locale.US); + } + private MaximalBodyDescriptor describe(byte[] mail) throws Exception { ByteArrayInputStream bias = new ByteArrayInputStream(mail); parser.parse(bias); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
