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 <garydgreg...@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 {