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 2024/07/08 12:36:04 UTC

(commons-io) 03/03: BOMInputStream.read() should return -1 after the stream is closed

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

commit b5c10499ecbd38b4e03dde074d15866ad0ccee0d
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Mon Jul 8 08:35:53 2024 -0400

    BOMInputStream.read() should return -1 after the stream is closed
---
 src/changes/changes.xml                            |  1 +
 .../apache/commons/io/input/BOMInputStream.java    |  3 +++
 .../apache/commons/io/input/ProxyInputStream.java  | 22 ++++++++++++++-----
 .../commons/io/input/BOMInputStreamTest.java       | 25 +++++++++++++++++++++-
 .../commons/io/input/ProxyInputStreamTest.java     | 24 +++++++++++++++++++--
 5 files changed, 67 insertions(+), 8 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ed4e56a81..1e6a278e3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -70,6 +70,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add"                due-to="Gary Gregory">InfiniteCircularInputStream.available() should return 0 when the stream is closed.</action>
       <action dev="ggregory" type="add"                due-to="Gary Gregory">ChecksumInputStream(InputStream, Checksum, long, long) should fail-fast on null Checksum input.</action>
       <action dev="ggregory" type="add"                due-to="Gary Gregory">BufferedFileChannelInputStream.read() should return -1 after the stream is closed.</action>
+      <action dev="ggregory" type="add"                due-to="Gary Gregory">BOMInputStream.read() should return -1 after the stream is closed.</action>
       <!-- UPDATE -->
       <action dev="ggregory" type="update"             due-to="Dependabot">Bump tests commons.bytebuddy.version from 1.14.13 to 1.14.17 #615, #621, #631, #635.</action>
       <action dev="ggregory" type="update"             due-to="Dependabot">Bump tests commons-codec:commons-codec from 1.16.1 to 1.17.0.</action>
diff --git a/src/main/java/org/apache/commons/io/input/BOMInputStream.java b/src/main/java/org/apache/commons/io/input/BOMInputStream.java
index bf1e43226..7bd305b70 100644
--- a/src/main/java/org/apache/commons/io/input/BOMInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/BOMInputStream.java
@@ -419,6 +419,9 @@ public class BOMInputStream extends ProxyInputStream {
      */
     @Override
     public int read() throws IOException {
+        if (isClosed()) {
+            return EOF;
+        }
         final int b = readFirstBytes();
         return b >= 0 ? b : in.read();
     }
diff --git a/src/main/java/org/apache/commons/io/input/ProxyInputStream.java b/src/main/java/org/apache/commons/io/input/ProxyInputStream.java
index 806c058de..1d51c6ac3 100644
--- a/src/main/java/org/apache/commons/io/input/ProxyInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ProxyInputStream.java
@@ -39,20 +39,22 @@ import org.apache.commons.io.IOUtils;
  * <li>notify a subclass that an exception was caught through {@link #handleIOException(IOException)}</li>
  * <li>{@link #unwrap()} itself</li>
  * </ul>
- * <p>
- * This class does not add any state (no additional instance variables).
- * </p>
  */
 public abstract class ProxyInputStream extends FilterInputStream {
 
+    /**
+     * Tracks whether {@link #close()} has been called or not.
+     */
+    private boolean closed;
+
     /**
      * Constructs a new ProxyInputStream.
      *
      * @param proxy  the InputStream to delegate to
      */
     public ProxyInputStream(final InputStream proxy) {
-        super(proxy);
         // the proxy is stored in a protected superclass variable named 'in'
+        super(proxy);
     }
 
     /**
@@ -87,7 +89,7 @@ public abstract class ProxyInputStream extends FilterInputStream {
      */
     @Override
     public int available() throws IOException {
-        if (in != null) {
+        if (in != null && !isClosed()) {
             try {
                 return in.available();
             } catch (final IOException e) {
@@ -130,6 +132,7 @@ public abstract class ProxyInputStream extends FilterInputStream {
     @Override
     public void close() throws IOException {
         IOUtils.close(in, this::handleIOException);
+        closed = true;
     }
 
     /**
@@ -147,6 +150,15 @@ public abstract class ProxyInputStream extends FilterInputStream {
         throw e;
     }
 
+    /**
+     * Tests whether this instance is closed.
+     *
+     * @return whether this instance is closed.
+     */
+    boolean isClosed() {
+        return closed;
+    }
+
     /**
      * Invokes the delegate's {@code mark(int)} method.
      *
diff --git a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
index 6bd9f5426..9ebb578b9 100644
--- a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
@@ -37,6 +37,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.commons.io.ByteOrderMark;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.SystemProperties;
 import org.junit.jupiter.api.Test;
 import org.w3c.dom.Document;
@@ -207,13 +208,35 @@ public class BOMInputStreamTest {
     }
 
     @Test
-    public void testAvailableWithBOM() throws Exception {
+    public void testAvailableWithBOMAfterOpen() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
         try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertEquals(7, in.available());
         }
     }
 
+    @Test
+    public void testAvailableWithBOMAfterClose() throws Exception {
+        final byte[] data = { 'A', 'B', 'C', 'D' };
+        final InputStream shadow;
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
+            assertEquals(7, in.available());
+            shadow = in;
+        }
+        assertEquals(0, shadow.available());
+    }
+
+    @Test
+    public void testReadAfterClose() throws Exception {
+        final byte[] data = { 'A', 'B', 'C', 'D' };
+        final InputStream shadow;
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
+            assertEquals(7, in.available());
+            shadow = in;
+        }
+        assertEquals(IOUtils.EOF, shadow.read());
+    }
+
     @Test
     public void testAvailableWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
diff --git a/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
index 61f284515..159d2a6c6 100644
--- a/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
@@ -56,8 +56,14 @@ public class ProxyInputStreamTest<T extends ProxyInputStream> {
         return CharSequenceInputStream.builder().setCharSequence("abc").get();
     }
 
-    protected void testEos(final T inputStream) {
-        // empty
+    @SuppressWarnings("resource")
+    @Test
+    public void testAvailableAfterClose() throws IOException {
+        final T shadow;
+        try (T inputStream = createFixture()) {
+            shadow = inputStream;
+        }
+        assertEquals(0, shadow.available());
     }
 
     @Test
@@ -82,6 +88,10 @@ public class ProxyInputStreamTest<T extends ProxyInputStream> {
         }
     }
 
+    protected void testEos(final T inputStream) {
+        // empty
+    }
+
     @Test
     public void testRead() throws IOException {
         try (T inputStream = createFixture()) {
@@ -97,6 +107,16 @@ public class ProxyInputStreamTest<T extends ProxyInputStream> {
         }
     }
 
+    @SuppressWarnings("resource")
+    @Test
+    public void testReadAfterClose() throws IOException {
+        final T shadow;
+        try (T inputStream = createFixture()) {
+            shadow = inputStream;
+        }
+        assertEquals(IOUtils.EOF, shadow.read());
+    }
+
     @Test
     public void testReadArrayAtMiddleFully() throws IOException {
         try (T inputStream = createFixture()) {