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 95dd0e63cc72db7be4ccbbf2552bd5b4cd584215
Author: Andriy Redko <[email protected]>
AuthorDate: Sat Jul 11 11:58:23 2026 -0400

    Add maxSize to BinaryDataProvider so the payload size could be constrained 
(to 1Gb by default) (#3301)
    
    (cherry picked from commit da5edccdb85138bad83858f05eba10dfe5df4212)
---
 .../java/org/apache/cxf/helpers/CastUtils.java     | 10 +++++++-
 .../main/java/org/apache/cxf/helpers/IOUtils.java  | 30 ++++++++++++++++++----
 .../java/org/apache/cxf/io/CachedOutputStream.java |  8 +++++-
 .../cxf/jaxrs/provider/BinaryDataProvider.java     |  7 ++++-
 .../cxf/jaxrs/provider/BinaryDataProviderTest.java | 19 ++++++++++++++
 .../cache/CacheControlClientReaderInterceptor.java |  9 +++++--
 .../jaxrs/client/cache/CacheControlFeature.java    |  7 +++++
 7 files changed, 80 insertions(+), 10 deletions(-)

diff --git a/core/src/main/java/org/apache/cxf/helpers/CastUtils.java 
b/core/src/main/java/org/apache/cxf/helpers/CastUtils.java
index 958ef5f5e44..74c3db05da9 100644
--- a/core/src/main/java/org/apache/cxf/helpers/CastUtils.java
+++ b/core/src/main/java/org/apache/cxf/helpers/CastUtils.java
@@ -92,5 +92,13 @@ public final class CastUtils {
         return (Map.Entry<T, U>)p;
     }
 
-
+    public static int cast(long value) {
+        if (value > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        } else if (value < Integer.MIN_VALUE) {
+            return Integer.MIN_VALUE;
+        } else {
+            return (int) value;
+        }
+    }
 }
diff --git a/core/src/main/java/org/apache/cxf/helpers/IOUtils.java 
b/core/src/main/java/org/apache/cxf/helpers/IOUtils.java
index be05fe9981b..37507334e2d 100644
--- a/core/src/main/java/org/apache/cxf/helpers/IOUtils.java
+++ b/core/src/main/java/org/apache/cxf/helpers/IOUtils.java
@@ -34,10 +34,14 @@ import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.util.Objects;
 
+import org.apache.cxf.common.util.SystemPropertyAction;
 import org.apache.cxf.io.CopyingOutputStream;
 import org.apache.cxf.io.Transferable;
 
 public final class IOUtils {
+    public static final int DEFAULT_BINARY_MAX_SIZE =
+        SystemPropertyAction.getInteger("org.apache.cxf.binary-max-size", 
1073741824 /* 1Gb */);
+
     public static final Charset UTF8_CHARSET = 
java.nio.charset.StandardCharsets.UTF_8;
     public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
 
@@ -176,7 +180,7 @@ public final class IOUtils {
         if (output instanceof CopyingOutputStream) {
             return ((CopyingOutputStream)output).copyFrom(input);
         }
-        return copy(input, output, DEFAULT_BUFFER_SIZE);
+        return copy(input, output, DEFAULT_BUFFER_SIZE, -1);
     }
 
     public static int copyAndCloseInput(final InputStream input,
@@ -189,7 +193,7 @@ public final class IOUtils {
     public static int copyAndCloseInput(final InputStream input,
             final OutputStream output, int bufferSize) throws IOException {
         try (InputStream in = input) {
-            return copy(in, output, bufferSize);
+            return copy(in, output, bufferSize, -1);
         }
     }
 
@@ -206,9 +210,13 @@ public final class IOUtils {
             copy(r, output, bufferSize);
         }
     }
+    
+    public static int copy(final InputStream input, final OutputStream output, 
int bufferSize) throws IOException {
+        return copy(input, output, bufferSize, -1);
+    }
 
     public static int copy(final InputStream input, final OutputStream output,
-            int bufferSize) throws IOException {
+            int bufferSize, int maxSize) throws IOException {
         Objects.requireNonNull(input, "The inputStream is required but null 
value was provided");
         Objects.requireNonNull(output, "The outputStream is required but null 
value was provided");
         int avail = input.available();
@@ -228,6 +236,10 @@ public final class IOUtils {
             output.write(buffer, 0, n);
             total += n;
             n = input.read(buffer);
+
+            if (maxSize > 0 /* -1 sets to unlimited */ && total > maxSize) {
+                throw new IOException("The total limit of " + maxSize + " 
bytes exceeded, data is too large");
+            }
         }
         return total;
     }
@@ -426,15 +438,23 @@ public final class IOUtils {
             n = input.read(buffer, 0, n);
         }
     }
-
+    
+    /**
+     * @deprecated use {@link #readBytesFromStream(InputStream, int)}
+     */
+    @Deprecated(forRemoval = true)
     public static byte[] readBytesFromStream(InputStream in) throws 
IOException {
+        return readBytesFromStream(in, -1);
+    }
+
+    public static byte[] readBytesFromStream(InputStream in, int maxSize) 
throws IOException {
         Objects.requireNonNull(in, "The inputStream is required but null value 
was provided");
         int i = in.available();
         if (i < DEFAULT_BUFFER_SIZE) {
             i = DEFAULT_BUFFER_SIZE;
         }
         try (InputStream input = in; ByteArrayOutputStream bos = new 
ByteArrayOutputStream(i)) {
-            copy(input, bos);
+            copy(input, bos, DEFAULT_BUFFER_SIZE, maxSize);
             return bos.toByteArray();
         }
     }
diff --git a/core/src/main/java/org/apache/cxf/io/CachedOutputStream.java 
b/core/src/main/java/org/apache/cxf/io/CachedOutputStream.java
index c19e7cfbe8b..200b0dfe248 100644
--- a/core/src/main/java/org/apache/cxf/io/CachedOutputStream.java
+++ b/core/src/main/java/org/apache/cxf/io/CachedOutputStream.java
@@ -44,6 +44,7 @@ import javax.crypto.CipherOutputStream;
 import org.apache.cxf.Bus;
 import org.apache.cxf.BusFactory;
 import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.helpers.CastUtils;
 import org.apache.cxf.helpers.FileUtils;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.helpers.LoadingByteArrayOutputStream;
@@ -334,9 +335,14 @@ public class CachedOutputStream extends OutputStream {
             }
             throw new IOException("Unknown format of currentStream");
         }
+
+        if (totalLength > Integer.MAX_VALUE) {
+            throw new IOException("The total limit of " + Integer.MAX_VALUE + 
" bytes exceeded, data is too large");
+        }
+
         // read the file
         try (InputStream fin = createInputStream(tempFile)) {
-            return IOUtils.readBytesFromStream(fin);
+            return IOUtils.readBytesFromStream(fin, CastUtils.cast(maxSize));
         }
     }
 
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/BinaryDataProvider.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/BinaryDataProvider.java
index f920988b467..80d534c58b4 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/BinaryDataProvider.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/BinaryDataProvider.java
@@ -72,6 +72,7 @@ public class BinaryDataProvider<T> extends 
AbstractConfigurableProvider
     private static final Logger LOG = 
LogUtils.getL7dLogger(BinaryDataProvider.class);
 
     private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
+    private int maxSize = IOUtils.DEFAULT_BINARY_MAX_SIZE;
     private boolean reportByteArraySize;
     private boolean closeResponseInputStream = true;
     public boolean isReadable(Class<?> type, Type genericType, Annotation[] 
annotations, MediaType mt) {
@@ -106,7 +107,7 @@ public class BinaryDataProvider<T> extends 
AbstractConfigurableProvider
                 return clazz.cast(new InputStreamReader(is, 
getEncoding(type)));
             }
             if (byte[].class.isAssignableFrom(clazz)) {
-                return clazz.cast(IOUtils.readBytesFromStream(is));
+                return clazz.cast(IOUtils.readBytesFromStream(is, maxSize));
             }
             if (File.class.isAssignableFrom(clazz)) {
                 LOG.warning("Reading data into File objects with the help of 
pre-packaged"
@@ -260,6 +261,10 @@ public class BinaryDataProvider<T> extends 
AbstractConfigurableProvider
         this.bufferSize = bufferSize;
     }
 
+    public void setMaxSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+    
     protected NioWriteHandler getNioHandler(final InputStream in) {
 
         return new NioWriteHandler() {
diff --git 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/BinaryDataProviderTest.java
 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/BinaryDataProviderTest.java
index c8f13010806..b60e27615fa 100644
--- 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/BinaryDataProviderTest.java
+++ 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/BinaryDataProviderTest.java
@@ -39,8 +39,11 @@ import org.apache.cxf.jaxrs.impl.MetadataMap;
 
 import org.junit.Test;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 public class BinaryDataProviderTest {
@@ -133,6 +136,22 @@ public class BinaryDataProviderTest {
         assertArrayEquals(os.toByteArray(), new String("hi").getBytes());
     }
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void testReadBytesMaxSize() throws Exception {
+        final int maxSize = 4096;
+        final BinaryDataProvider p = new BinaryDataProvider();
+        p.setMaxSize(maxSize);
+
+        final byte[] bytes = new byte[maxSize + 1]; /* maxSize + 1 byte */
+        final IOException ex = assertThrows(IOException.class, 
+            () -> p.readFrom(byte[].class, byte[].class, new Annotation[]{},
+                  MediaType.APPLICATION_OCTET_STREAM_TYPE,
+                  new MetadataMap<String, Object>(),
+                  new ByteArrayInputStream(bytes)));
+
+        assertThat(ex.getMessage(), equalTo("The total limit of 4096 bytes 
exceeded, data is too large"));
+    }
 
     private static final class StreamingOutputImpl implements StreamingOutput {
 
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
index 3da097aa99b..8fc8cea373b 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
@@ -49,6 +49,7 @@ public class CacheControlClientReaderInterceptor implements 
ReaderInterceptor {
     @Context
     private UriInfo uriInfo;
     private boolean cacheResponseInputStream;
+    private int maxSize = IOUtils.DEFAULT_BINARY_MAX_SIZE;
 
     public CacheControlClientReaderInterceptor(final Cache<Key, Entry> cache) {
         this.cache = cache;
@@ -63,6 +64,10 @@ public class CacheControlClientReaderInterceptor implements 
ReaderInterceptor {
         return this;
     }
 
+    public void setMaxSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
     @Override
     public Object aroundReadFrom(final ReaderInterceptorContext context) 
throws IOException, WebApplicationException {
         Object cachedEntity = 
context.getProperty(CacheControlClientRequestFilter.CACHED_ENTITY_PROPERTY);
@@ -97,7 +102,7 @@ public class CacheControlClientReaderInterceptor implements 
ReaderInterceptor {
         final boolean validCacheControl = isCacheControlValid(context, 
cacheControl);
         if (validCacheControl && cacheResponseInputStream) {
             // if Cache-Control is set and the stream needs to be cached then 
do it
-            cachedBytes = 
IOUtils.readBytesFromStream(context.getInputStream());
+            cachedBytes = 
IOUtils.readBytesFromStream(context.getInputStream(), maxSize);
             context.setInputStream(new ByteArrayInputStream(cachedBytes));
         }
         // Read the stream and get the actual entity
@@ -117,7 +122,7 @@ public class CacheControlClientReaderInterceptor implements 
ReaderInterceptor {
         } else if (responseEntity instanceof InputStream) {
             // read the stream, cache it, the cached bytes will be returned 
immediately
             // when a client cache will return them
-            byte[] bytes = 
IOUtils.readBytesFromStream((InputStream)responseEntity);
+            byte[] bytes = 
IOUtils.readBytesFromStream((InputStream)responseEntity, maxSize);
             ser = new BytesEntity(bytes, true);
             responseEntity = new ByteArrayInputStream(bytes);
         }
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
index 0b2877889d7..841e3c4a005 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
@@ -39,6 +39,7 @@ import javax.ws.rs.core.Feature;
 import javax.ws.rs.core.FeatureContext;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.cxf.helpers.IOUtils;
 
 @Provider
 public class CacheControlFeature implements Feature, Closeable {
@@ -46,6 +47,7 @@ public class CacheControlFeature implements Feature, 
Closeable {
     private CacheManager manager;
     private Cache<Key, Entry> cache;
     private boolean cacheResponseInputStream;
+    private int maxSize = IOUtils.DEFAULT_BINARY_MAX_SIZE;
 
     @Override
     public boolean configure(final FeatureContext context) {
@@ -54,10 +56,15 @@ public class CacheControlFeature implements Feature, 
Closeable {
         context.register(new CacheControlClientRequestFilter(entryCache));
         CacheControlClientReaderInterceptor reader = new 
CacheControlClientReaderInterceptor(entryCache);
         reader.setCacheResponseInputStream(cacheResponseInputStream);
+        reader.setMaxSize(maxSize);
         context.register(reader);
         return true;
     }
 
+    public void setMaxSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
     @PreDestroy // TODO: check it is called
     public void close() {
         for (final Closeable c : Arrays.asList(cache, manager, provider)) {

Reply via email to