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 d8aa672222b1752e6fd1ad9485eccc5b09e329a8
Author: Benoit TELLIER <[email protected]>
AuthorDate: Thu Nov 5 05:26:45 2026 +0100

    [FIX] RawField#getBody: delete CR and LF before decoding, as RawFieldParser
    
    getBody decoded the bytes as UTF-8 and then unfolded the text, whereas
    RawFieldParser leaves line breaks out of the bytes it decodes. A bare CR
    inside a multi-byte sequence therefore split it for the strict parsers
    only: filename="caf<C3><CR><A9>.txt" read caf<U+FFFD><U+FFFD>.txt strict
    and café.txt lenient. getBody now deletes the line breaks from the bytes
    first, so that both paths agree.
---
 .../org/apache/james/mime4j/stream/RawField.java   | 28 +++++++++++++++++++++-
 .../field/BareLineBreakStrictLenientTest.java      | 28 ++++++++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/james/mime4j/stream/RawField.java 
b/core/src/main/java/org/apache/james/mime4j/stream/RawField.java
index 879278bc..a508f69f 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/RawField.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/RawField.java
@@ -22,6 +22,7 @@ package org.apache.james.mime4j.stream;
 import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 
+import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.CharsetUtil;
 import org.apache.james.mime4j.util.ContentUtil;
@@ -161,11 +162,36 @@ public final class RawField implements Field {
             if (len > off && (CharsetUtil.isWhitespace((char) (raw.byteAt(off) 
& 0xff)))) {
                 off++;
             }
-            return MimeUtil.unfold(ContentUtil.decode(raw, off, len - off, 
StandardCharsets.UTF_8));
+            return decodeUnfolded(raw, off, len);
         }
         return null;
     }
 
+    /**
+     * Deletes CR and LF from the bytes before decoding, as RawFieldParser 
does: a line break inside
+     * a multi-byte sequence must not split it.
+     */
+    private static String decodeUnfolded(ByteSequence raw, int off, int len) {
+        ByteArrayBuffer unfolded = null;
+        for (int i = off; i < len; i++) {
+            byte b = raw.byteAt(i);
+            if (b == '\r' || b == '\n') {
+                if (unfolded == null) {
+                    unfolded = new ByteArrayBuffer(len - off);
+                    for (int j = off; j < i; j++) {
+                        unfolded.append(raw.byteAt(j));
+                    }
+                }
+            } else if (unfolded != null) {
+                unfolded.append(b);
+            }
+        }
+        if (unfolded == null) {
+            return ContentUtil.decode(raw, off, len - off, 
StandardCharsets.UTF_8);
+        }
+        return ContentUtil.decode(unfolded, 0, unfolded.length(), 
StandardCharsets.UTF_8);
+    }
+
     public int getDelimiterIdx() {
         return delimiterIdx;
     }
diff --git 
a/dom/src/test/java/org/apache/james/mime4j/field/BareLineBreakStrictLenientTest.java
 
b/dom/src/test/java/org/apache/james/mime4j/field/BareLineBreakStrictLenientTest.java
index 5fc1bd06..b88cfb3c 100644
--- 
a/dom/src/test/java/org/apache/james/mime4j/field/BareLineBreakStrictLenientTest.java
+++ 
b/dom/src/test/java/org/apache/james/mime4j/field/BareLineBreakStrictLenientTest.java
@@ -19,14 +19,20 @@
 
 package org.apache.james.mime4j.field;
 
+import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.field.ContentDispositionField;
 import org.apache.james.mime4j.dom.field.ContentLanguageField;
 import org.apache.james.mime4j.dom.field.ContentLocationField;
 import org.apache.james.mime4j.dom.field.ContentTypeField;
 import org.apache.james.mime4j.dom.field.MimeVersionField;
+import org.apache.james.mime4j.stream.RawField;
+import org.apache.james.mime4j.stream.RawFieldParser;
+import org.apache.james.mime4j.util.ByteArrayBuffer;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
+
 /**
  * A CR or LF that is not part of a fold is deleted by MimeUtil#unfold, which 
the strict parsers
  * read: the lenient parsers must not take it as a word separator either.
@@ -89,4 +95,26 @@ public class BareLineBreakStrictLenientTest {
         Assert.assertEquals(raw, strict.getDispositionType(), 
lenient.getDispositionType());
         Assert.assertEquals(raw, strict.getParameter(parameter), 
lenient.getParameter(parameter));
     }
+
+    @Test
+    public void 
bareCarriageReturnInsideAUtf8SequenceShouldBeReadTheSameWayByBothParsers() 
throws Exception {
+        assertFilename("caf\u00e9.txt", "Content-Disposition: a; 
filename=\"caf\u00c3\r\u00a9.txt\"");
+        assertFilename("caf\u00e9.txt", "Content-Disposition: a; 
filename=caf\u00c3\r\u00a9.txt");
+    }
+
+    @Test
+    public void 
bareLineFeedInsideAUtf8SequenceShouldBeReadTheSameWayByBothParsers() throws 
Exception {
+        assertFilename("caf\u00e9.txt", "Content-Disposition: a; 
filename=\"caf\u00c3\n\u00a9.txt\"");
+    }
+
+    private static void assertFilename(String expected, String latin1) throws 
Exception {
+        RawField field = RawFieldParser.DEFAULT.parseField(
+            new ByteArrayBuffer(latin1.getBytes(StandardCharsets.ISO_8859_1), 
true));
+
+        ContentDispositionField strict = (ContentDispositionField) 
DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
+        ContentDispositionField lenient = (ContentDispositionField) 
LenientFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
+
+        Assert.assertEquals(expected, lenient.getFilename());
+        Assert.assertEquals(expected, strict.getFilename());
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to