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 static Map.Entry cast(Map.Entry p, Class pc, Class< return (Map.Entry)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.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 static int copy(final InputStream input, final OutputStream output) 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 static int copyAndCloseInput(final InputStream input, 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 static void copyAndCloseInput(final Reader input, 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 static int copy(final InputStream input, final OutputStream output, 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 static void consume(final InputStream input, 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 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 byte[] getBytes() throws IOException { } 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 d9604618d10..4d521f806b7 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 @@ -71,6 +71,7 @@ public class BinaryDataProvider 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) { @@ -105,7 +106,7 @@ public T readFrom(Class clazz, Type genericType, Annotation[] annotations, Me 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" @@ -259,6 +260,10 @@ public void setBufferSize(int bufferSize) { 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 02d648c0971..b3c8395d3a5 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.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 void testWriteTo() throws Exception { 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(), + 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 c3f303921d2..04729bce260 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 cache) { this.cache = cache; @@ -63,6 +64,10 @@ public CacheControlClientReaderInterceptor setCache(final Cache c) { 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 Object aroundReadFrom(final ReaderInterceptorContext context) throws IOEx 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 Object aroundReadFrom(final ReaderInterceptorContext context) throws IOEx } 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 37bbc3ac573..bdb8cab1a8a 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,7 +39,7 @@ import jakarta.ws.rs.core.Feature; import jakarta.ws.rs.core.FeatureContext; import jakarta.ws.rs.ext.Provider; - +import org.apache.cxf.helpers.IOUtils; @Provider public class CacheControlFeature implements Feature, Closeable { @@ -47,6 +47,7 @@ public class CacheControlFeature implements Feature, Closeable { private CacheManager manager; private Cache cache; private boolean cacheResponseInputStream; + private int maxSize = IOUtils.DEFAULT_BINARY_MAX_SIZE; @Override public boolean configure(final FeatureContext context) { @@ -55,10 +56,15 @@ public boolean configure(final FeatureContext context) { 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)) {