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 b90149fb8520ac6d7839a45625d7c769c4266a0c
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Nov 4 15:12:49 2026 +0100

    [FIX] RawFieldParser: a quoted pair in a quoted boundary stands for the 
escaped character
    
    copyQuotedContent keeps the backslash of a quoted pair before any
    character other than a quote or a backslash, so boundary="a\bc" was read
    as a\bc by the lenient parsers and MimeTokenStream, while the strict DOM
    parser reads abc: the two split the multipart on different delimiters.
    The boundary already has its own path (no encoded-word decoding); it now
    drops the backslash of every quoted pair too, as RFC 2046 bchars hold no
    backslash. Other parameters are unchanged.
    
    Fixes #108
---
 .../apache/james/mime4j/stream/RawFieldParser.java | 22 ++++++++++++----------
 .../james/mime4j/stream/RawFieldParserTest.java    | 12 ++++++++++++
 .../mime4j/field/LenientContentTypeFieldTest.java  | 12 ++++++++++++
 3 files changed, 36 insertions(+), 10 deletions(-)

diff --git 
a/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java 
b/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
index 13c17953..22044939 100644
--- a/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
+++ b/core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
@@ -166,8 +166,9 @@ public class RawFieldParser {
         if (delim == ';') {
             return new NameValuePair(name, null);
         }
-        // The boundary is a byte exact delimiter: never decode it as an 
encoded word
-        String value = parseValue(buf, cursor, SEMICOLON, 
!"boundary".equalsIgnoreCase(name));
+        // The boundary is a byte exact delimiter: never decode it as an 
encoded word,
+        // and a quoted pair always stands for the escaped character alone
+        String value = parseValue(buf, cursor, SEMICOLON, 
"boundary".equalsIgnoreCase(name));
         if (!cursor.atEnd()) {
             cursor.updatePos(cursor.getPos() + 1);
         }
@@ -217,11 +218,11 @@ public class RawFieldParser {
      *  is not delimited by any character.
      */
     public String parseValue(final ByteSequence buf, final ParserCursor 
cursor, final BitSet delimiters) {
-        return parseValue(buf, cursor, delimiters, true);
+        return parseValue(buf, cursor, delimiters, false);
     }
 
     private String parseValue(final ByteSequence buf, final ParserCursor 
cursor, final BitSet delimiters,
-            final boolean decodeEncodedWords) {
+            final boolean boundary) {
         StringBuilder dst = new StringBuilder();
         boolean whitespace = false;
         while (!cursor.atEnd()) {
@@ -237,7 +238,7 @@ public class RawFieldParser {
                 if (dst.length() > 0 && whitespace) {
                     dst.append(' ');
                 }
-                copyQuotedContent(buf, cursor, dst, decodeEncodedWords);
+                copyQuotedContent(buf, cursor, dst, boundary);
                 whitespace = false;
             } else {
                 if (dst.length() > 0 && whitespace) {
@@ -449,7 +450,7 @@ public class RawFieldParser {
      */
     public void copyQuotedContent(final ByteSequence buf, final ParserCursor 
cursor,
             final StringBuilder dst) {
-        copyQuotedContent(buf, cursor, dst, true);
+        copyQuotedContent(buf, cursor, dst, false);
     }
 
     /**
@@ -471,7 +472,7 @@ public class RawFieldParser {
     }
 
     private void copyQuotedContent(final ByteSequence buf, final ParserCursor 
cursor,
-            final StringBuilder dst, final boolean decodeEncodedWords) {
+            final StringBuilder dst, final boolean boundary) {
         if (cursor.atEnd()) {
             return;
         }
@@ -497,7 +498,8 @@ public class RawFieldParser {
                     // A fold is not part of the value: the quoted pair 
applies to the WSP after it
                     continue;
                 }
-                if (current != '\"' && current != '\\') {
+                // A boundary cannot hold a backslash (RFC 2046 bchars): the 
quoted pair is unescaped
+                if (!boundary && current != '\"' && current != '\\') {
                     dstRaw.append('\\');
                 }
                 dstRaw.append(currentByte);
@@ -517,7 +519,7 @@ public class RawFieldParser {
 
         if (CharsetUtil.isASCII(dstRaw)) {
             // Check for encoded word directly on bytes — avoids an 
intermediate String
-            if (decodeEncodedWords && dstRaw.length() > 1 && dstRaw.byteAt(0) 
== '=' && dstRaw.byteAt(1) == '?') {
+            if (!boundary && dstRaw.length() > 1 && dstRaw.byteAt(0) == '=' && 
dstRaw.byteAt(1) == '?') {
                 String raw = ContentUtil.decode(dstRaw);
                 dst.append(DecoderUtil.decodeEncodedWords(raw, 
DecodeMonitor.SILENT));
             } else {
@@ -528,7 +530,7 @@ public class RawFieldParser {
             }
         } else {
             String decoded = ContentUtil.decode(StandardCharsets.UTF_8, 
dstRaw);
-            if (decodeEncodedWords && decoded.startsWith("=?")) {
+            if (!boundary && decoded.startsWith("=?")) {
                 decoded = DecoderUtil.decodeEncodedWords(decoded, 
DecodeMonitor.SILENT);
             }
             dst.append(decoded);
diff --git 
a/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java 
b/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
index d9e042ec..ed285332 100644
--- a/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
+++ b/core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
@@ -544,6 +544,18 @@ public class RawFieldParserTest {
         Assert.assertEquals("a\\\r\"; size=1.txt", 
body.getParams().get(0).getValue());
     }
 
+    @Test
+    public void quotedPairInBoundaryShouldBeUnescaped() throws Exception {
+        ByteSequence raw = ContentUtil.encode("multipart/mixed; 
boundary=\"a\\bc\"; name=\"a\\bc\"");
+        RawBody body = parser.parseRawBody(raw, new ParserCursor(0, 
raw.length()));
+        Assert.assertEquals("abc", body.getParams().get(0).getValue());
+        Assert.assertEquals("a\\bc", body.getParams().get(1).getValue());
+
+        FallbackBodyDescriptorBuilder builder = new 
FallbackBodyDescriptorBuilder();
+        builder.addField(new RawField("Content-Type", "multipart/mixed; 
boundary=\"a\\bc\""));
+        Assert.assertEquals("abc", builder.build().getBoundary());
+    }
+
     @Test
     public void fieldNameShouldNotHoldComments() throws Exception {
         RawField renamed = parser.parseField(ContentUtil.encode("Subj(x)ect: 
hello"));
diff --git 
a/dom/src/test/java/org/apache/james/mime4j/field/LenientContentTypeFieldTest.java
 
b/dom/src/test/java/org/apache/james/mime4j/field/LenientContentTypeFieldTest.java
index 08c832eb..0a62e2fb 100644
--- 
a/dom/src/test/java/org/apache/james/mime4j/field/LenientContentTypeFieldTest.java
+++ 
b/dom/src/test/java/org/apache/james/mime4j/field/LenientContentTypeFieldTest.java
@@ -108,4 +108,16 @@ public class LenientContentTypeFieldTest {
         Assert.assertEquals(strict.getMimeType(), lenient.getMimeType());
         Assert.assertEquals("message/rfc822", parse("Content-Type:  
message/rfc822 ").getMimeType());
     }
+
+    @Test
+    public void quotedPairInBoundaryShouldBeUnescapedLikeTheStrictParser() 
throws Exception {
+        String raw = "Content-Type: multipart/mixed; boundary=\"a\\bc\"; 
name=\"a\\bc\"";
+        ContentTypeField lenient = parse(raw);
+        ContentTypeField strict = (ContentTypeField) 
DefaultFieldParser.parse(raw);
+
+        Assert.assertEquals("abc", strict.getBoundary());
+        Assert.assertEquals("abc", lenient.getBoundary());
+        // other parameters keep the backslash before an ordinary character, 
as before
+        Assert.assertEquals("a\\bc", lenient.getParameter("name"));
+    }
 }


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

Reply via email to