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 d755c5012b16b018b0512e98cf3ccb41d5b54453 Author: Andriy Redko <[email protected]> AuthorDate: Fri Jul 17 18:26:15 2026 -0400 Make sure MessageContextImpl respects the limit to the maximum number of attachments to be collected (#3311) (cherry picked from commit 0607af83c10405e5cde10f676526254a255e321b) --- .../cxf/attachment/AttachmentDeserializer.java | 3 +- .../apache/cxf/jaxrs/ext/MessageContextImpl.java | 27 ++++- .../cxf/jaxrs/provider/MultipartProviderTest.java | 134 +++++++++++++++++++++ 3 files changed, 161 insertions(+), 3 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 71fdbd080ba..5429d675862 100644 --- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java +++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java @@ -69,6 +69,7 @@ public class AttachmentDeserializer { * The maximum number of attachments permitted in a message. The default is 50. */ public static final String ATTACHMENT_MAX_COUNT = "attachment-max-count"; + public static final int DEFAULT_ATTACHMENT_MAX_COUNT = 50; /** * The maximum number of attachment headers permitted in a message. The default is 500. @@ -134,7 +135,7 @@ public class AttachmentDeserializer { initializeRootMessage(); Object maxCountProperty = message.getContextualProperty(AttachmentDeserializer.ATTACHMENT_MAX_COUNT); - int maxCount = 50; + int maxCount = DEFAULT_ATTACHMENT_MAX_COUNT; if (maxCountProperty != null) { if (maxCountProperty instanceof Integer) { maxCount = (Integer)maxCountProperty; diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java index 644800adcfd..bdf61df2d97 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java @@ -274,6 +274,8 @@ public class MessageContextImpl implements MessageContext { m.getExchange().getInMessage().get(AttachmentDeserializer.ATTACHMENT_MAX_SIZE)); inMessage.put(AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE, m.getExchange().getInMessage().get(AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE)); + inMessage.put(AttachmentDeserializer.ATTACHMENT_MAX_COUNT, + m.getExchange().getInMessage().get(AttachmentDeserializer.ATTACHMENT_MAX_COUNT)); inMessage.setContent(InputStream.class, m.getExchange().getInMessage().get("org.apache.cxf.multipart.embedded.input")); inMessage.put(Message.CONTENT_TYPE, @@ -283,6 +285,16 @@ public class MessageContextImpl implements MessageContext { new AttachmentInputInterceptor().handleMessage(inMessage); + final Object maxCountProperty = inMessage.getContextualProperty(AttachmentDeserializer.ATTACHMENT_MAX_COUNT); + int maxAttachmentCount = AttachmentDeserializer.DEFAULT_ATTACHMENT_MAX_COUNT; + if (maxCountProperty != null) { + if (maxCountProperty instanceof Integer) { + maxAttachmentCount = (Integer)maxCountProperty; + } else { + maxAttachmentCount = Integer.parseInt((String)maxCountProperty); + } + } + List<Attachment> newAttachments = new LinkedList<>(); try { Map<String, List<String>> headers @@ -294,6 +306,9 @@ public class MessageContextImpl implements MessageContext { inMessage), new ProvidersImpl(inMessage)); newAttachments.add(first); + if (newAttachments.size() > maxAttachmentCount) { + throw new IOException("The message contains more attachments than are permitted"); + } } catch (IOException ex) { throw ExceptionUtils.toInternalServerErrorException(ex, null); } @@ -303,9 +318,17 @@ public class MessageContextImpl implements MessageContext { if (childAttachments == null) { childAttachments = Collections.emptyList(); } - for (org.apache.cxf.message.Attachment a : childAttachments) { - newAttachments.add(new Attachment(a, new ProvidersImpl(inMessage))); + try { + for (org.apache.cxf.message.Attachment a : childAttachments) { + newAttachments.add(new Attachment(a, new ProvidersImpl(inMessage))); + if (newAttachments.size() > maxAttachmentCount) { + throw new IOException("The message contains more attachments than are permitted"); + } + } + } catch (IOException ex) { + throw ExceptionUtils.toInternalServerErrorException(ex, null); } + MediaType mt = embeddedAttachment ? (MediaType)inMessage.get("org.apache.cxf.multipart.embedded.ctype") : getHttpHeaders().getMediaType(); diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java new file mode 100644 index 00000000000..b3396e5903e --- /dev/null +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java @@ -0,0 +1,134 @@ +/** + * 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.jaxrs.provider; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.stream.IntStream; + +import javax.ws.rs.core.MediaType; + +import org.apache.cxf.attachment.AttachmentDeserializer; +import org.apache.cxf.jaxrs.ext.MessageContextImpl; +import org.apache.cxf.jaxrs.impl.MetadataMap; +import org.apache.cxf.message.Exchange; +import org.apache.cxf.message.ExchangeImpl; +import org.apache.cxf.message.Message; +import org.apache.cxf.message.MessageImpl; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +public class MultipartProviderTest { + @Test + public void testChangingMaxAttachmentCount() throws Exception { + final Exchange exchange = new ExchangeImpl(); + final MultipartProvider p = new MultipartProvider(); + + StringBuilder sb = new StringBuilder(1000); + sb.append("SomeHeader: foo\n") + .append("------=_Part_34950_1098328613.1263781527359\n") + .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"); + + // Add many attachments + IntStream.range(0, 40).forEach(i -> { + sb.append("------=_Part_34950_1098328613.1263781527359\n") + .append("Content-Type: text/xml\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-Id: <b86a5f2d-e7af-4e5e-b71a-9f6f2307cab0>\n") + .append('\n') + .append("<message>\n") + .append("------=_Part_34950_1098328613.1263781527359--\n"); + }); + + // Too many attachments we'll not allow it + final Message msg = new MessageImpl(); + msg.setExchange(exchange); + exchange.setInMessage(msg); + p.setMessageContext(new MessageContextImpl(msg)); + + msg.put(AttachmentDeserializer.ATTACHMENT_MAX_COUNT, "30"); + msg.setContent(InputStream.class, new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8))); + msg.put(Message.CONTENT_TYPE, "multipart/related"); + + assertThrows("Failure expected on too many attachments", RuntimeException.class, + () -> p.readFrom(Object.class, Object.class, new Annotation[]{}, + MediaType.APPLICATION_OCTET_STREAM_TYPE, + new MetadataMap<String, String>(), + msg.getContent(InputStream.class))); + + // Now we'll allow it + final Message msg2 = new MessageImpl(); + msg2.setExchange(exchange); + exchange.setInMessage(msg2); + p.setMessageContext(new MessageContextImpl(msg2)); + + msg2.put(AttachmentDeserializer.ATTACHMENT_MAX_COUNT, "60"); + msg2.setContent(InputStream.class, new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8))); + msg2.put(Message.CONTENT_TYPE, "multipart/related"); + + Map<?, ?> body = (Map<?, ?>) p.readFrom(Object.class, Object.class, new Annotation[]{}, + MediaType.APPLICATION_OCTET_STREAM_TYPE, + new MetadataMap<String, String>(), + msg2.getContent(InputStream.class)); + + // Force it to load the attachments + assertEquals(3, body.size()); + } + + @Test + public void testManyAttachmentHeaders() throws Exception { + final Exchange exchange = new ExchangeImpl(); + final MultipartProvider p = new MultipartProvider(); + + 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"); + + final Message msg = new MessageImpl(); + msg.setExchange(exchange); + exchange.setInMessage(msg); + p.setMessageContext(new MessageContextImpl(msg)); + + msg.setContent(InputStream.class, new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8))); + msg.put(Message.CONTENT_TYPE, "multipart/related"); + + assertThrows("Failure expected on too many attachment headers", RuntimeException.class, + () -> p.readFrom(Object.class, Object.class, new Annotation[]{}, + MediaType.APPLICATION_OCTET_STREAM_TYPE, + new MetadataMap<String, String>(), + msg.getContent(InputStream.class))); + } +}
