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

Reply via email to