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]

Reply via email to