From 77a6939eb915672c25aa9bca7a95ac1759f29b5d Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Sun, 6 Sep 2026 11:28:52 -0400 Subject: [PATCH] Add formParamsMaxSize to FormEncodingProvider and estimate form params count before decoding / parsing --- .../jaxrs/provider/FormEncodingProvider.java | 9 ++++-- .../org/apache/cxf/jaxrs/utils/FormUtils.java | 28 ++++++++++++++++++- .../jaxrs/JAXRSClientServerBookTest.java | 26 +++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java index bb798c42a54..76ff7d59cc3 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java @@ -60,6 +60,7 @@ public class FormEncodingProvider extends AbstractConfigurableProvider private String attachmentDir; private String attachmentThreshold; private String attachmentMaxSize; + private int formParamsMaxSize = FormUtils.DEFAULT_FORM_PARAMS_MAX_SIZE; private boolean expectEncoded; @@ -86,6 +87,10 @@ public void setAttachmentThreshold(String threshold) { public void setAttachmentMaxSize(String maxSize) { attachmentMaxSize = maxSize; } + + public void setFormParamsMaxSize(int maxSize) { + this.formParamsMaxSize = maxSize; + } public void setValidator(FormValidator formValidator) { validator = formValidator; @@ -171,13 +176,13 @@ protected void populateMap(MultivaluedMap params, if (servletRequest == null) { FormUtils.populateMapFromString(params, PhaseInterceptorChain.getCurrentMessage(), - FormUtils.readBody(is, enc), + FormUtils.readBody(is, enc, formParamsMaxSize), enc, decode); } else { FormUtils.populateMapFromString(params, PhaseInterceptorChain.getCurrentMessage(), - FormUtils.readBody(is, enc), + FormUtils.readBody(is, enc, formParamsMaxSize), enc, decode, (jakarta.servlet.http.HttpServletRequest)servletRequest); diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java index 44f3a6d0b7e..3479f737e95 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java @@ -41,6 +41,7 @@ import jakarta.ws.rs.core.MultivaluedMap; 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.IOUtils; import org.apache.cxf.io.CachedOutputStream; import org.apache.cxf.jaxrs.ext.multipart.Attachment; @@ -54,6 +55,9 @@ import org.apache.cxf.transport.http.AbstractHTTPDestination; public final class FormUtils { + public static final int DEFAULT_FORM_PARAMS_MAX_SIZE = + SystemPropertyAction.getInteger("org.apache.cxf.form-params-max-size", 104857600 /* 100Mb */); + public static final int DEFAULT_MAX_FORM_PARAM_COUNT = 500; public static final String FORM_PARAMS_FROM_HTTP_PARAMS = "set.form.parameters.from.http.parameters"; @@ -116,10 +120,18 @@ public static void addPropertyToForm(MultivaluedMap map, String } } + /** + * @deprecated please use {@code readBody(InputStream is, String encoding, int maxSize)} + */ + @Deprecated public static String readBody(InputStream is, String encoding) { + return readBody(is, encoding, -1); + } + + public static String readBody(InputStream is, String encoding, int maxSize) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - IOUtils.copy(is, bos, 1024); + IOUtils.copy(is, bos, 1024, maxSize); return new String(bos.toByteArray(), encoding); } catch (Exception ex) { throw ExceptionUtils.toInternalServerErrorException(ex, null); @@ -134,6 +146,8 @@ public static void populateMapFromString(MultivaluedMap params, if (StringUtils.isEmpty(postBody)) { return; } + final int numberOfParts = estimateNumberOfParts(postBody); + checkNumberOfParts(m, numberOfParts); String[] parts = postBody.split("&"); checkNumberOfParts(m, parts.length); for (String part : parts) { @@ -288,6 +302,18 @@ public static void populateMapFromMultipart(MultivaluedMap param } } + /** + * Estimates how many parts we should expect by checking on & separator + */ + private static int estimateNumberOfParts(String postBody) { + int count = 0; + int index = -1; + while ((index = postBody.indexOf('&', index + 1)) >= 0) { + ++count; + } + return count; + } + private static void checkNumberOfParts(Message m, int numberOfParts) { if (m == null || m.getExchange() == null || m.getExchange().getInMessage() == null) { return; diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java index 64cc125d07f..4b0b1d9d498 100644 --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java @@ -36,6 +36,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.zip.GZIPInputStream; import javax.xml.namespace.QName; @@ -342,6 +344,30 @@ public void testPostEmptyFormAsInStream() throws Exception { assertEquals("empty form", r.readEntity(String.class)); } + @Test + public void testTooManyFormParams() throws Exception { + // Exceeding 500 limit + String params = IntStream.range(0, 501).mapToObj(i -> "param" + i + "=" + i).collect(Collectors.joining("&")); + String address = "http://localhost:" + PORT + "/bookstore/form"; + WebClient wc = WebClient.create(address); + wc.type(MediaType.APPLICATION_FORM_URLENCODED); + Response r = wc.post(new ByteArrayInputStream(params.getBytes(StandardCharsets.UTF_8))); + assertThat("max form params limit reached", r.getStatus(), equalTo(413)); + } + + @Test + public void testTooLargeFormParams() throws Exception { + // Exceeding 100Mb limit + String params = IntStream.range(0, 110) + .mapToObj(i -> "param" + i + "=" + + new String(Integer.toString(i)).repeat(524800)).collect(Collectors.joining("&")); + String address = "http://localhost:" + PORT + "/bookstore/form"; + WebClient wc = WebClient.create(address); + wc.type(MediaType.APPLICATION_FORM_URLENCODED); + Response r = wc.post(new ByteArrayInputStream(params.getBytes(StandardCharsets.UTF_8))); + assertThat("max form params limit reached", r.getStatus(), equalTo(500)); + } + @Test public void testGetBookDescriptionHttpResponse() throws Exception { String address = "http://localhost:" + PORT + "/bookstore/httpresponse";