This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch 3.6.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit a2f43e5c475ff6f7dd5ab23388ae8e827d05a8db
Author: Andriy Redko <[email protected]>
AuthorDate: Tue Jun 2 11:36:41 2026 -0400

    Add limit to the maximum number of attachment headers to be collected 
(#3159)
---
 .../cxf/attachment/AttachmentDeserializer.java     | 116 ++-----------
 .../cxf/attachment/AttachmentDeserializerUtil.java | 181 +++++++++++++++++++++
 .../cxf/attachment/AttachmentDeserializerTest.java |  23 +++
 3 files changed, 219 insertions(+), 101 deletions(-)

diff --git 
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
index 7827b8d5014..dfe76e6b4b1 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
@@ -30,14 +30,11 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
-import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import javax.activation.DataSource;
 
-import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.common.util.SystemPropertyAction;
 import org.apache.cxf.helpers.HttpHeaderHelper;
@@ -71,6 +68,13 @@ public class AttachmentDeserializer {
      */
     public static final String ATTACHMENT_MAX_COUNT = "attachment-max-count";
 
+    /**
+     * The maximum number of attachment headers permitted in a message. The 
default is 500.
+     */
+    public static final String ATTACHMENT_HEADERS_MAX_COUNT = 
"attachment-headers-max-count";
+    public static final int DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT =
+        
SystemPropertyAction.getInteger("org.apache.cxf.attachment-max-headers-count", 
500);
+
     /**
      * The maximum MIME Header Length. The default is 300.
      */
@@ -85,8 +89,6 @@ public class AttachmentDeserializer {
     private static final Pattern INPUT_STREAM_BOUNDARY_PATTERN =
             Pattern.compile("^--(\\S*)$", Pattern.MULTILINE);
 
-    private static final Logger LOG = 
LogUtils.getL7dLogger(AttachmentDeserializer.class);
-
     private static final int PUSHBACK_AMOUNT = 2048;
 
     private boolean lazyLoading = true;
@@ -108,6 +110,7 @@ public class AttachmentDeserializer {
     private List<String> supportedTypes;
 
     private int maxHeaderLength = DEFAULT_MAX_HEADER_SIZE;
+    private int maxHeadersCount = DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT;
 
     public AttachmentDeserializer(Message message) {
         this(message, Collections.singletonList("multipart/related"));
@@ -120,6 +123,9 @@ public class AttachmentDeserializer {
         // Get the maximum Header length from configuration
         maxHeaderLength = MessageUtils.getContextualInteger(message, 
ATTACHMENT_MAX_HEADER_SIZE,
                                                             
DEFAULT_MAX_HEADER_SIZE);
+        // Get the maximum headers count
+        maxHeadersCount = MessageUtils.getContextualInteger(message, 
ATTACHMENT_HEADERS_MAX_COUNT,
+                                                            
DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT);
     }
 
     public void initializeAttachments() throws IOException {
@@ -166,7 +172,8 @@ public class AttachmentDeserializer {
                 throw new IOException("Couldn't find MIME boundary: " + 
boundaryString);
             }
 
-            Map<String, List<String>> ih = loadPartHeaders(stream);
+            final Map<String, List<String>> ih = AttachmentDeserializerUtil
+                .loadPartHeaders(stream, maxHeaderLength, maxHeadersCount);
             message.put(ATTACHMENT_PART_HEADERS, ih);
             String val = AttachmentUtil.getHeader(ih, "Content-Type", "; ");
             if (!StringUtils.isEmpty(val)) {
@@ -231,7 +238,8 @@ public class AttachmentDeserializer {
         }
         stream.unread(v);
 
-        Map<String, List<String>> headers = loadPartHeaders(stream);
+        final Map<String, List<String>> headers = AttachmentDeserializerUtil
+            .loadPartHeaders(stream, maxHeaderLength, maxHeadersCount);
         return (AttachmentImpl)createAttachment(headers);
     }
 
@@ -367,98 +375,4 @@ public class AttachmentDeserializer {
         stream.unread(v);
         return true;
     }
-
-
-
-    private Map<String, List<String>> loadPartHeaders(InputStream in) throws 
IOException {
-        StringBuilder buffer = new StringBuilder(128);
-        StringBuilder b = new StringBuilder(128);
-        Map<String, List<String>> heads = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
-        // loop until we hit the end or a null line
-        while (readLine(in, b)) {
-            // lines beginning with white space get special handling
-            char c = b.charAt(0);
-            if (c == ' ' || c == '\t') {
-                if (buffer.length() != 0) {
-                    // preserve the line break and append the continuation
-                    buffer.append("\r\n");
-                    buffer.append(b);
-                }
-            } else {
-                // if we have a line pending in the buffer, flush it
-                if (buffer.length() > 0) {
-                    addHeaderLine(heads, buffer);
-                    buffer.setLength(0);
-                }
-                // add this to the accumulator
-                buffer.append(b);
-            }
-        }
-
-        // if we have a line pending in the buffer, flush it
-        if (buffer.length() > 0) {
-            addHeaderLine(heads, buffer);
-        }
-        return heads;
-    }
-
-    private boolean readLine(InputStream in, StringBuilder buffer) throws 
IOException {
-        if (buffer.length() != 0) {
-            buffer.setLength(0);
-        }
-        int c;
-
-        while ((c = in.read()) != -1) {
-            // a linefeed is a terminator, always.
-            if (c == '\n') {
-                break;
-            } else if (c == '\r') {
-                //just ignore the CR.  The next character SHOULD be an NL.  If 
not, we're
-                //just going to discard this
-                continue;
-            } else {
-                // just add to the buffer
-                buffer.append((char)c);
-            }
-
-            if (buffer.length() > maxHeaderLength) {
-                LOG.fine("The attachment header size has exceeded the 
configured parameter: " + maxHeaderLength);
-                throw new HeaderSizeExceededException();
-            }
-        }
-
-        // no characters found...this was either an eof or a null line.
-        return buffer.length() != 0;
-    }
-
-    private void addHeaderLine(Map<String, List<String>> heads, StringBuilder 
line) {
-        // null lines are a nop
-        final int size = line.length();
-        if (size == 0) {
-            return;
-        }
-        int separator = line.indexOf(":");
-        final String name;
-        String value = "";
-        if (separator == -1) {
-            name = line.toString().trim();
-        } else {
-            name = line.substring(0, separator);
-            // step past the separator.  Now we need to remove any leading 
white space characters.
-            separator++;
-
-            while (separator < size) {
-                char ch = line.charAt(separator);
-                if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
-                    break;
-                }
-                separator++;
-            }
-            value = line.substring(separator);
-        }
-        List<String> v = heads.computeIfAbsent(name, k -> new ArrayList<>(1));
-        v.add(value);
-    }
-
 }
diff --git 
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
new file mode 100644
index 00000000000..c74d70c2962
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
@@ -0,0 +1,181 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.attachment;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.logging.LogUtils;
+
+final class AttachmentDeserializerUtil {
+    /* Keep the log under AttachmentDeserializer */
+    private static final Logger LOG = 
LogUtils.getL7dLogger(AttachmentDeserializer.class);
+
+    private AttachmentDeserializerUtil() {
+    }
+    
+    /**
+     * Move the read pointer to the begining of the first part read till the 
end
+     * of first boundary
+     *
+     * @param pushbackInStream
+     * @param boundary
+     * @throws IOException
+     */
+    static boolean readTillFirstBoundary(PushbackInputStream pushbackInStream,
+        byte[] boundary) throws IOException {
+
+        // work around a bug in PushBackInputStream where the buffer isn't
+        // initialized
+        // and available always returns 0.
+        int value = pushbackInStream.read();
+        pushbackInStream.unread(value);
+        while (value != -1) {
+            value = pushbackInStream.read();
+            if ((byte) value == boundary[0]) {
+                int boundaryIndex = 0;
+                while (value != -1 
+                    && boundaryIndex < boundary.length 
+                    && (byte)value == boundary[boundaryIndex]) {
+
+                    value = pushbackInStream.read();
+                    if (value == -1) {
+                        throw new IOException("Unexpected End while searching 
for first Mime Boundary");
+                    }
+                    boundaryIndex++;
+                }
+                if (boundaryIndex == boundary.length) {
+                    // boundary found, read the newline
+                    if (value == 13) {
+                        pushbackInStream.read();
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    static Map<String, List<String>> loadPartHeaders(InputStream in, int 
maxHeaderLength, 
+            int maxHeadersCount) throws IOException {
+        StringBuilder buffer = new StringBuilder(128);
+        StringBuilder b = new StringBuilder(128);
+        Map<String, List<String>> heads = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+        // loop until we hit the end or a null line
+        while (readLine(in, b, maxHeaderLength)) {
+            // lines beginning with white space get special handling
+            char c = b.charAt(0);
+            if (c == ' ' || c == '\t') {
+                if (buffer.length() != 0) {
+                    // preserve the line break and append the continuation
+                    buffer.append("\r\n");
+                    buffer.append(b);
+                }
+            } else {
+                // if we have a line pending in the buffer, flush it
+                if (buffer.length() > 0) {
+                    addHeaderLine(heads, buffer, maxHeadersCount);
+                    buffer.setLength(0);
+                }
+                // add this to the accumulator
+                buffer.append(b);
+            }
+        }
+
+        // if we have a line pending in the buffer, flush it
+        if (buffer.length() > 0) {
+            addHeaderLine(heads, buffer, maxHeadersCount);
+        }
+        return heads;
+    }
+
+    private static boolean readLine(InputStream in, StringBuilder buffer, int 
maxHeaderLength) throws IOException {
+        if (buffer.length() != 0) {
+            buffer.setLength(0);
+        }
+        int c;
+
+        while ((c = in.read()) != -1) {
+            // a linefeed is a terminator, always.
+            if (c == '\n') {
+                break;
+            } else if (c == '\r') {
+                //just ignore the CR.  The next character SHOULD be an NL.  If 
not, we're
+                //just going to discard this
+                continue;
+            } else {
+                // just add to the buffer
+                buffer.append((char)c);
+            }
+
+            if (buffer.length() > maxHeaderLength) {
+                LOG.fine("The attachment header size has exceeded the 
configured parameter: " + maxHeaderLength);
+                throw new HeaderSizeExceededException();
+            }
+        }
+
+        // no characters found...this was either an eof or a null line.
+        return buffer.length() != 0;
+    }
+
+    private static void addHeaderLine(Map<String, List<String>> heads, 
StringBuilder line, 
+            int maxHeadersCount) throws IOException {
+        // null lines are a nop
+        final int size = line.length();
+        if (size == 0) {
+            return;
+        }
+        int separator = line.indexOf(":");
+        final String name;
+        String value = "";
+        if (separator == -1) {
+            name = line.toString().trim();
+        } else {
+            name = line.substring(0, separator);
+            // step past the separator.  Now we need to remove any leading 
white space characters.
+            separator++;
+
+            while (separator < size) {
+                char ch = line.charAt(separator);
+                if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
+                    break;
+                }
+                separator++;
+            }
+            value = line.substring(separator);
+        }
+        
+        if (heads.size() >= maxHeadersCount) {
+            throw new IOException("The attachment contains more headers than 
are permitted");
+        }
+        List<String> v = heads.computeIfAbsent(name, k -> new ArrayList<>(1));
+        v.add(value);
+    }
+
+
+}
diff --git 
a/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java 
b/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
index 675b0233d5d..a244f44a2d4 100644
--- 
a/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
+++ 
b/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
@@ -20,6 +20,7 @@ package org.apache.cxf.attachment;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PushbackInputStream;
 import java.nio.charset.StandardCharsets;
@@ -667,6 +668,28 @@ public class AttachmentDeserializerTest {
         ins.close();
     }
 
+    @Test
+    public void testManyAttachmentHeaders() throws Exception {
+        StringBuilder sb = new StringBuilder(10000);
+        // Add many attachment headers
+        sb.append("------=_Part_34950_1098328613.1263781527359\n");
+        IntStream.range(0, 1000).forEach(i -> 
sb.append("Header-").append(i).append(": foo").append(i).append('\n'));
+        sb.append("Content-Type: text/xml; charset=UTF-8\n")
+            .append("Content-Transfer-Encoding: binary\n")
+            .append("Content-Id: 
<318731183421.1263781527359.IBM.WEBSERVICES@auhpap02>\n")
+            .append('\n')
+            .append("<envelope/>\n");
+
+        msg = new MessageImpl();
+        msg.setContent(InputStream.class, new 
ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)));
+        msg.put(Message.CONTENT_TYPE, "multipart/related");
+        AttachmentDeserializer ad = new AttachmentDeserializer(msg);
+
+        assertThrows("Failure expected on too many attachment headers", 
IOException.class, 
+            () -> ad.initializeAttachments());
+    }
+
+    
     @Test
     public void testManyAttachments() throws Exception {
         StringBuilder sb = new StringBuilder(1000);

Reply via email to