diff --git a/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java index 72675a0e5cd..e7bdc7d616d 100644 --- a/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java +++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java @@ -38,12 +38,17 @@ public class AttachmentBoundaryDeserializer { private static final int PUSHBACK_AMOUNT = 2048; private final int maxHeaderLength; + private final int maxHeadersCount; private final Message message; public AttachmentBoundaryDeserializer(Message message) { this.message = message; this.maxHeaderLength = MessageUtils.getContextualInteger(message, AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE, AttachmentDeserializer.DEFAULT_MAX_HEADER_SIZE); + // Get the maximum headers count + this.maxHeadersCount = MessageUtils.getContextualInteger(message, + AttachmentDeserializer.ATTACHMENT_HEADERS_MAX_COUNT, + AttachmentDeserializer.DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT); } public Attachment read(InputStream body) throws IOException { @@ -60,7 +65,8 @@ public Attachment read(InputStream body) throws IOException { throw new IOException("Couldn't find MIME boundary: " + boundaryString); } - Map> ih = AttachmentDeserializerUtil.loadPartHeaders(stream, maxHeaderLength); + final Map> ih = AttachmentDeserializerUtil + .loadPartHeaders(stream, maxHeaderLength, maxHeadersCount); String val = AttachmentUtil.getHeader(ih, "Content-Transfer-Encoding"); MimeBodyPartInputStream mmps = new MimeBodyPartInputStream(stream, boundary, PUSHBACK_AMOUNT); 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 d3b87411eac..d5c6409cb56 100644 --- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java +++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java @@ -67,6 +67,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. */ @@ -102,6 +109,7 @@ public class AttachmentDeserializer { private List 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")); @@ -114,6 +122,9 @@ public AttachmentDeserializer(Message message, List supportedTypes) { // 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 { @@ -160,7 +171,8 @@ protected void initializeRootMessage() throws IOException { throw new IOException("Couldn't find MIME boundary: " + boundaryString); } - Map> ih = AttachmentDeserializerUtil.loadPartHeaders(stream, maxHeaderLength); + final Map> ih = AttachmentDeserializerUtil + .loadPartHeaders(stream, maxHeaderLength, maxHeadersCount); message.put(ATTACHMENT_PART_HEADERS, ih); String val = AttachmentUtil.getHeader(ih, "Content-Type", "; "); if (!StringUtils.isEmpty(val)) { @@ -225,7 +237,8 @@ public AttachmentImpl readNext() throws IOException { } stream.unread(v); - Map> headers = AttachmentDeserializerUtil.loadPartHeaders(stream, maxHeaderLength); + final Map> headers = AttachmentDeserializerUtil + .loadPartHeaders(stream, maxHeaderLength, maxHeadersCount); return (AttachmentImpl)createAttachment(headers); } diff --git a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java index 98ee9a468c2..c74d70c2962 100644 --- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java +++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java @@ -80,7 +80,8 @@ static boolean readTillFirstBoundary(PushbackInputStream pushbackInStream, } - static Map> loadPartHeaders(InputStream in, int maxHeaderLength) throws IOException { + static Map> loadPartHeaders(InputStream in, int maxHeaderLength, + int maxHeadersCount) throws IOException { StringBuilder buffer = new StringBuilder(128); StringBuilder b = new StringBuilder(128); Map> heads = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -98,7 +99,7 @@ static Map> loadPartHeaders(InputStream in, int maxHeaderLe } else { // if we have a line pending in the buffer, flush it if (buffer.length() > 0) { - addHeaderLine(heads, buffer); + addHeaderLine(heads, buffer, maxHeadersCount); buffer.setLength(0); } // add this to the accumulator @@ -108,7 +109,7 @@ static Map> loadPartHeaders(InputStream in, int maxHeaderLe // if we have a line pending in the buffer, flush it if (buffer.length() > 0) { - addHeaderLine(heads, buffer); + addHeaderLine(heads, buffer, maxHeadersCount); } return heads; } @@ -142,7 +143,8 @@ private static boolean readLine(InputStream in, StringBuilder buffer, int maxHea return buffer.length() != 0; } - private static void addHeaderLine(Map> heads, StringBuilder line) { + private static void addHeaderLine(Map> heads, StringBuilder line, + int maxHeadersCount) throws IOException { // null lines are a nop final int size = line.length(); if (size == 0) { @@ -167,6 +169,10 @@ private static void addHeaderLine(Map> heads, StringBuilder } value = line.substring(separator); } + + if (heads.size() >= maxHeadersCount) { + throw new IOException("The attachment contains more headers than are permitted"); + } List 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 70657184808..eec76dbba9c 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 @@ 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 void testCXF3582c() throws Exception { 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("\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);