This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new d51cc4c98a9f CAMEL-25023: camel-util - count copied bytes in a long in
IOHelper.copy so a maxDecompressedSize of 2 GiB or more is enforced
d51cc4c98a9f is described below
commit d51cc4c98a9fe98f2c5c7d148eea0d5526445fb3
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 28 19:00:19 2026 +0200
CAMEL-25023: camel-util - count copied bytes in a long in IOHelper.copy so
a maxDecompressedSize of 2 GiB or more is enforced
IOHelper.copy counted the copied bytes in an int. Past Integer.MAX_VALUE
the count wrapped around to a negative number, so a maxSize of 2 GiB or
more (such as a maxDecompressedSize configured that high) was never
reached and a larger stream was copied in full.
The count is now a long, so the limit is enforced at any size. The method
still returns an int, capped at Integer.MAX_VALUE when more bytes than
that were copied.
Closes #26892
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../main/java/org/apache/camel/util/IOHelper.java | 11 ++--
.../java/org/apache/camel/util/IOHelperTest.java | 76 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 11 ++++
3 files changed, 94 insertions(+), 4 deletions(-)
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
index bf539d8a83b4..7c8c5c984daf 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
@@ -206,8 +206,10 @@ public final class IOHelper {
* @param output the output stream buffer
* @param bufferSize the size of the buffer used for the copies
* @param flushOnEachWrite whether to flush the data everytime that data
is written to the buffer
- * @return the number of bytes copied
- * @throws IOException for I/O errors
+ * @param maxSize the maximum number of bytes allowed to be
copied, or 0 or less for no limit
+ * @return the number of bytes copied, or {@link
Integer#MAX_VALUE} if more bytes than that were
+ * copied
+ * @throws IOException for I/O errors, or if more than maxSize bytes
are copied
*/
public static int copy(
final InputStream input, final OutputStream output, int
bufferSize, boolean flushOnEachWrite,
@@ -236,7 +238,8 @@ public final class IOHelper {
bufferSize, flushOnEachWrite);
}
- int total = 0;
+ // count in a long so that a maxSize of 2 GiB or more is enforced (an
int would wrap around)
+ long total = 0;
final byte[] buffer = new byte[bufferSize];
int n = input.read(buffer);
@@ -266,7 +269,7 @@ public final class IOHelper {
// flush at end, if we didn't do it during the writing
output.flush();
}
- return total;
+ return (int) Math.min(total, Integer.MAX_VALUE);
}
/**
diff --git
a/core/camel-util/src/test/java/org/apache/camel/util/IOHelperTest.java
b/core/camel-util/src/test/java/org/apache/camel/util/IOHelperTest.java
index c7c820ef0412..87013be37ab9 100644
--- a/core/camel-util/src/test/java/org/apache/camel/util/IOHelperTest.java
+++ b/core/camel-util/src/test/java/org/apache/camel/util/IOHelperTest.java
@@ -16,12 +16,23 @@
*/
package org.apache.camel.util;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class IOHelperTest {
+ private static final long GIGABYTE = 1024L * 1024 * 1024;
+ private static final String MAX_SIZE_EXCEEDED = "The InputStream entry
being copied exceeds the maximum allowed size";
+
@Test
public void testLookupEnvironmentVariable() {
assertEquals("8081",
IOHelper.lookupEnvironmentVariable("FOO_SERVICE_PORT"));
@@ -42,4 +53,69 @@ public class IOHelperTest {
assertEquals("UTF-8",
IOHelper.getCharsetNameFromContentType("application/json; charset=utf-8"));
assertEquals("iso-8859-1",
IOHelper.getCharsetNameFromContentType("application/json; charset=iso-8859-1"));
}
+
+ @Test
+ public void testCopyMaxSize() throws IOException {
+ byte[] data = new byte[100];
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ assertEquals(100, IOHelper.copy(new ByteArrayInputStream(data), bos,
IOHelper.DEFAULT_BUFFER_SIZE, false, 100));
+ assertEquals(100, bos.size());
+
+ IOException e = assertThrows(IOException.class,
+ () -> IOHelper.copy(new ByteArrayInputStream(data), new
ByteArrayOutputStream(),
+ IOHelper.DEFAULT_BUFFER_SIZE, false, 99));
+ assertEquals(MAX_SIZE_EXCEEDED, e.getMessage());
+ }
+
+ @Test
+ public void testCopyMaxSizeOfTwoGigabytesOrMore() {
+ // the count of copied bytes must not wrap around once more than
Integer.MAX_VALUE bytes have been copied
+ for (long maxSize : new long[] { Integer.MAX_VALUE - 100L,
Integer.MAX_VALUE, 2 * GIGABYTE, 5 * GIGABYTE }) {
+ InputStream is = new ZeroInputStream(maxSize + GIGABYTE);
+ IOException e = assertThrows(IOException.class,
+ () -> IOHelper.copy(is, OutputStream.nullOutputStream(),
IOHelper.DEFAULT_BUFFER_SIZE, false, maxSize),
+ "maxSize " + maxSize + " was not enforced");
+ assertEquals(MAX_SIZE_EXCEEDED, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCopyMoreThanTwoGigabytes() throws IOException {
+ InputStream is = new ZeroInputStream(3 * GIGABYTE);
+ assertEquals(Integer.MAX_VALUE,
+ IOHelper.copy(is, OutputStream.nullOutputStream(),
IOHelper.DEFAULT_BUFFER_SIZE, false, -1));
+ }
+
+ /**
+ * Returns the given number of zero bytes without holding them in memory.
+ */
+ private static final class ZeroInputStream extends InputStream {
+
+ private long remaining;
+
+ ZeroInputStream(long size) {
+ this.remaining = size;
+ }
+
+ @Override
+ public int read() {
+ if (remaining <= 0) {
+ return -1;
+ }
+ remaining--;
+ return 0;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) {
+ if (remaining <= 0) {
+ return -1;
+ }
+ int n = (int) Math.min(len, remaining);
+ Arrays.fill(b, off, off + n, (byte) 0);
+ remaining -= n;
+ return n;
+ }
+ }
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 96ade35bd1bf..432875ded31b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2869,6 +2869,17 @@ To restore the previous behaviour and allow external
entity resolution, set the
`allowExternalEntities` option to `true` on the data format or on the endpoint
(`smooks:config.xml?allowExternalEntities=true`).
+=== camel-zipfile, camel-tarfile - a maxDecompressedSize of 2 GiB or more is
now enforced
+
+The `maxDecompressedSize` option of the Zip File and Tar File data formats
(and of `ZipSplitter`) was not
+enforced when set to 2 GiB (`Integer.MAX_VALUE` bytes) or more, because the
byte count behind the check
+wrapped around. The limit is now enforced for any value: unmarshalling an
entry that decompresses to more
+than the configured `maxDecompressedSize` fails with an `IOException`, as
documented. The default limit
+(1 GiB) is unchanged, and `-1` still disables the limit.
+
+`IOHelper.copy(InputStream, OutputStream, int, boolean, long)` now returns
`Integer.MAX_VALUE`, instead of a
+negative number, when it copies more than `Integer.MAX_VALUE` bytes.
+
=== camel-core - resolveResource option on expressions
Every expression has a new option `resolveResource` (default `false`). When
`true` and the expression's result is a