This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new 9eeee192257b [backport camel-4.22.x] CAMEL-25023: camel-util - count
copied bytes in a long in IOHelper.copy (#27137)
9eeee192257b is described below
commit 9eeee192257bec5ed5c713ae193e2cf9f72541cb
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Sep 30 21:35:18 2026 +0200
[backport camel-4.22.x] CAMEL-25023: camel-util - count copied bytes in a
long in IOHelper.copy (#27137)
Backport of #26892 from main.
The byte counter behind the maxSize check in
IOHelper.copy(InputStream, OutputStream, int, boolean, long) was an int, so
a maxDecompressedSize
of 2 GiB (Integer.MAX_VALUE) or more wrapped around to a negative value and
the check never fired.
On this branch the guard is present and bypassable: with the fix reverted
the added tests fail with
"maxSize 2147483547 was not enforced" and a returned count of -1073741824.
Counting in a long fixes it; the method still returns an int, now clamped
to Integer.MAX_VALUE
rather than a wrapped negative.
The upgrade-guide entry stays on main, per the backport upgrade-guide
policy.
Co-Authored-By: Claude Opus 5 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../main/java/org/apache/camel/util/IOHelper.java | 11 ++--
.../java/org/apache/camel/util/IOHelperTest.java | 76 ++++++++++++++++++++++
2 files changed, 83 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;
+ }
+ }
}