You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/04/05 14:45:17 UTC

[commons-io] branch master updated: Let subclasses detect when reading past the maximum is requested

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new f7e0f2c0 Let subclasses detect when reading past the maximum is requested
f7e0f2c0 is described below

commit f7e0f2c05503e619a64d026e30d6cb08c4917842
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Tue Apr 4 18:37:35 2023 -0400

    Let subclasses detect when reading past the maximum is requested
    
    Javadoc
---
 .../commons/io/input/BoundedInputStream.java       | 102 +++++++++++++++------
 .../commons/io/input/BoundedInputStreamTest.java   |  55 +++++++++++
 2 files changed, 128 insertions(+), 29 deletions(-)

diff --git a/src/main/java/org/apache/commons/io/input/BoundedInputStream.java b/src/main/java/org/apache/commons/io/input/BoundedInputStream.java
index 51bfb2eb..39bfa19e 100644
--- a/src/main/java/org/apache/commons/io/input/BoundedInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/BoundedInputStream.java
@@ -22,15 +22,11 @@ import java.io.IOException;
 import java.io.InputStream;
 
 /**
- * This is a stream that will only supply bytes up to a certain length - if its
- * position goes above that, it will stop.
+ * Reads bytes up to a maximum length, if its count goes above that, it stops.
  * <p>
- * This is useful to wrap ServletInputStreams. The ServletInputStream will block
- * if you try to read content from it that isn't there, because it doesn't know
- * whether the content hasn't arrived yet or whether the content has finished.
- * So, one of these, initialized with the Content-length sent in the
- * ServletInputStream's header, will stop it blocking, providing it's been sent
- * with a correct content length.
+ * This is useful to wrap ServletInputStreams. The ServletInputStream will block if you try to read content from it that isn't there, because it doesn't know
+ * whether the content hasn't arrived yet or whether the content has finished. So, one of these, initialized with the Content-length sent in the
+ * ServletInputStream's header, will stop it blocking, providing it's been sent with a correct content length.
  * </p>
  *
  * @since 2.0
@@ -40,11 +36,11 @@ public class BoundedInputStream extends InputStream {
     /** The wrapped input stream. */
     private final InputStream inputStream;
 
-    /** The max length to read. */
-    private final long maxLength;
+    /** The max count of bytes to read. */
+    private final long maxCount;
 
-    /** The number of bytes already returned. */
-    private long pos;
+    /** The count of bytes read. */
+    private long count;
 
     /** The marked position. */
     private long mark = EOF;
@@ -53,26 +49,26 @@ public class BoundedInputStream extends InputStream {
     private boolean propagateClose = true;
 
     /**
-     * Creates a new {@link BoundedInputStream} that wraps the given input
+     * Constructs a new {@link BoundedInputStream} that wraps the given input
      * stream and is unlimited.
      *
-     * @param in The wrapped input stream
+     * @param in The wrapped input stream.
      */
     public BoundedInputStream(final InputStream in) {
         this(in, EOF);
     }
 
     /**
-     * Creates a new {@link BoundedInputStream} that wraps the given input
+     * Constructs a new {@link BoundedInputStream} that wraps the given input
      * stream and limits it to a certain size.
      *
-     * @param inputStream The wrapped input stream
-     * @param maxLength The maximum number of bytes to return
+     * @param inputStream The wrapped input stream.
+     * @param maxLength The maximum number of bytes to return.
      */
     public BoundedInputStream(final InputStream inputStream, final long maxLength) {
         // Some badly designed methods - e.g. the servlet API - overload length
         // such that "-1" means stream finished
-        this.maxLength = maxLength;
+        this.maxCount = maxLength;
         this.inputStream = inputStream;
     }
 
@@ -81,7 +77,8 @@ public class BoundedInputStream extends InputStream {
      */
     @Override
     public int available() throws IOException {
-        if (maxLength >= 0 && pos >= maxLength) {
+        if (isMaxLength()) {
+            onMaxLength(maxCount, count);
             return 0;
         }
         return inputStream.available();
@@ -90,6 +87,7 @@ public class BoundedInputStream extends InputStream {
     /**
      * Invokes the delegate's {@code close()} method
      * if {@link #isPropagateClose()} is {@code true}.
+     *
      * @throws IOException if an I/O error occurs.
      */
     @Override
@@ -100,7 +98,31 @@ public class BoundedInputStream extends InputStream {
     }
 
     /**
-     * Indicates whether the {@link #close()} method
+     * Gets the count of bytes read.
+     *
+     * @return The count of bytes read.
+     * @since 2.12.0
+     */
+    public long getCount() {
+        return count;
+    }
+
+    /**
+     * Gets the max count of bytes to read.
+     *
+     * @return The max count of bytes to read.
+     * @since 2.12.0
+     */
+    public long getMaxLength() {
+        return maxCount;
+    }
+
+    private boolean isMaxLength() {
+        return maxCount >= 0 && count >= maxCount;
+    }
+
+    /**
+     * Tests whether the {@link #close()} method
      * should propagate to the underling {@link InputStream}.
      *
      * @return {@code true} if calling {@link #close()}
@@ -113,16 +135,18 @@ public class BoundedInputStream extends InputStream {
 
     /**
      * Invokes the delegate's {@code mark(int)} method.
+     *
      * @param readlimit read ahead limit
      */
     @Override
     public synchronized void mark(final int readlimit) {
         inputStream.mark(readlimit);
-        mark = pos;
+        mark = count;
     }
 
     /**
      * Invokes the delegate's {@code markSupported()} method.
+     *
      * @return true if mark is supported, otherwise false
      */
     @Override
@@ -130,25 +154,40 @@ public class BoundedInputStream extends InputStream {
         return inputStream.markSupported();
     }
 
+    /**
+     * A caller has caused a request that would cross the {@code maxLength} boundary.
+     *
+     * @param maxLength The max count of bytes to read.
+     * @param count The count of bytes read.
+     * @throws IOException Subclasses may throw.
+     * @since 2.12.0
+     */
+    protected void onMaxLength(final long maxLength, final long count) throws IOException {
+        // for subclasses
+    }
+
     /**
      * Invokes the delegate's {@code read()} method if
      * the current position is less than the limit.
+     *
      * @return the byte read or -1 if the end of stream or
      * the limit has been reached.
      * @throws IOException if an I/O error occurs.
      */
     @Override
     public int read() throws IOException {
-        if (maxLength >= 0 && pos >= maxLength) {
+        if (isMaxLength()) {
+            onMaxLength(maxCount, count);
             return EOF;
         }
         final int result = inputStream.read();
-        pos++;
+        count++;
         return result;
     }
 
     /**
      * Invokes the delegate's {@code read(byte[])} method.
+     *
      * @param b the buffer to read the bytes into
      * @return the number of bytes read or -1 if the end of stream or
      * the limit has been reached.
@@ -161,6 +200,7 @@ public class BoundedInputStream extends InputStream {
 
     /**
      * Invokes the delegate's {@code read(byte[], int, int)} method.
+     *
      * @param b the buffer to read the bytes into
      * @param off The start offset
      * @param len The number of bytes to read
@@ -170,28 +210,30 @@ public class BoundedInputStream extends InputStream {
      */
     @Override
     public int read(final byte[] b, final int off, final int len) throws IOException {
-        if (maxLength >= 0 && pos >= maxLength) {
+        if (isMaxLength()) {
+            onMaxLength(maxCount, count);
             return EOF;
         }
-        final long maxRead = maxLength >= 0 ? Math.min(len, maxLength - pos) : len;
+        final long maxRead = maxCount >= 0 ? Math.min(len, maxCount - count) : len;
         final int bytesRead = inputStream.read(b, off, (int) maxRead);
 
         if (bytesRead == EOF) {
             return EOF;
         }
 
-        pos += bytesRead;
+        count += bytesRead;
         return bytesRead;
     }
 
     /**
      * Invokes the delegate's {@code reset()} method.
+     *
      * @throws IOException if an I/O error occurs.
      */
     @Override
     public synchronized void reset() throws IOException {
         inputStream.reset();
-        pos = mark;
+        count = mark;
     }
 
     /**
@@ -209,20 +251,22 @@ public class BoundedInputStream extends InputStream {
 
     /**
      * Invokes the delegate's {@code skip(long)} method.
+     *
      * @param n the number of bytes to skip
      * @return the actual number of bytes skipped
      * @throws IOException if an I/O error occurs.
      */
     @Override
     public long skip(final long n) throws IOException {
-        final long toSkip = maxLength >= 0 ? Math.min(n, maxLength - pos) : n;
+        final long toSkip = maxCount >= 0 ? Math.min(n, maxCount - count) : n;
         final long skippedBytes = inputStream.skip(toSkip);
-        pos += skippedBytes;
+        count += skippedBytes;
         return skippedBytes;
     }
 
     /**
      * Invokes the delegate's {@code toString()} method.
+     *
      * @return the delegate's {@code toString()}
      */
     @Override
diff --git a/src/test/java/org/apache/commons/io/input/BoundedInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BoundedInputStreamTest.java
index 3d47be97..126b519a 100644
--- a/src/test/java/org/apache/commons/io/input/BoundedInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/BoundedInputStreamTest.java
@@ -17,8 +17,11 @@
 package org.apache.commons.io.input;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.ByteArrayInputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.Test;
@@ -35,6 +38,58 @@ public class BoundedInputStreamTest {
         }
     }
 
+    @Test
+    public void testOnMaxLength() throws Exception {
+        BoundedInputStream bounded;
+        final byte[] helloWorld = "Hello World".getBytes();
+        final byte[] hello = "Hello".getBytes();
+        final AtomicBoolean boolRef = new AtomicBoolean();
+
+        // limit = length
+        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length) {
+            @Override
+            protected void onMaxLength(long max, long readCount) {
+                boolRef.set(true);
+            }
+        };
+        assertFalse(boolRef.get());
+        for (int i = 0; i < helloWorld.length; i++) {
+            assertEquals(helloWorld[i], bounded.read(), "limit = length byte[" + i + "]");
+        }
+        assertEquals(-1, bounded.read(), "limit = length end");
+        assertTrue(boolRef.get());
+
+        // limit > length
+        boolRef.set(false);
+        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length + 1) {
+            @Override
+            protected void onMaxLength(long max, long readCount) {
+                boolRef.set(true);
+            }
+        };
+        assertFalse(boolRef.get());
+        for (int i = 0; i < helloWorld.length; i++) {
+            assertEquals(helloWorld[i], bounded.read(), "limit > length byte[" + i + "]");
+        }
+        assertEquals(-1, bounded.read(), "limit > length end");
+        assertFalse(boolRef.get());
+
+        // limit < length
+        boolRef.set(false);
+        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), hello.length) {
+            @Override
+            protected void onMaxLength(long max, long readCount) {
+                boolRef.set(true);
+            }
+        };
+        assertFalse(boolRef.get());
+        for (int i = 0; i < hello.length; i++) {
+            assertEquals(hello[i], bounded.read(), "limit < length byte[" + i + "]");
+        }
+        assertEquals(-1, bounded.read(), "limit < length end");
+        assertTrue(boolRef.get());
+    }
+
     @Test
     public void testReadArray() throws Exception {