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 2021/05/12 15:05:49 UTC

[commons-io] branch master updated: Prevent infinite loop with AbstractCharacterFilterReader if EOF is filtered out #226.

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 0f6da44  Prevent infinite loop with AbstractCharacterFilterReader if EOF is filtered out #226.
0f6da44 is described below

commit 0f6da4475714d869feab5768f34536f8855e3bd8
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed May 12 11:05:44 2021 -0400

    Prevent infinite loop with AbstractCharacterFilterReader if EOF is
    filtered out #226.
    
    - Based on PR #226 by Rob Spoor, with an additional (missing) test, and
    clean ups.
    - Add CharacterSetFilterReader.CharacterSetFilterReader(Reader,
    Integer...).
---
 src/changes/changes.xml                            |  8 ++++-
 .../io/input/AbstractCharacterFilterReader.java    |  8 ++---
 .../commons/io/input/CharacterSetFilterReader.java | 27 ++++++++++++-----
 .../io/input/CharacterFilterReaderTest.java        | 29 +++++++++++++++---
 .../io/input/CharacterSetFilterReaderTest.java     | 34 ++++++++++++++++++----
 5 files changed, 82 insertions(+), 24 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index a385d2a..77f9567 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -123,6 +123,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-720" dev="ggregory" type="fix" due-to="XenoAmess">
         Fix error about usage of DirectBuffer in JRE 16/17 #205.
       </action>
+      <action dev="ggregory" type="fix" due-to="Rob Spoor, Gary Gregory">
+        Prevent infinite loop with AbstractCharacterFilterReader if EOF is filtered out #226.
+      </action>
       <!-- ADD -->
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add FileSystemProviders class.
@@ -193,9 +196,12 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add copy(URL, OutputStream).
       </action>
-      <action  issue="IO-651" dev="ggregory" type="add" due-to="jmark109, Gary Gregory">
+      <action issue="IO-651" dev="ggregory" type="add" due-to="jmark109, Gary Gregory">
         Add DeferredFileOutputStream.toInputStream() #206.
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add CharacterSetFilterReader.CharacterSetFilterReader(Reader, Integer...).
+      </action>
       <!-- UPDATES -->
       <action dev="ggregory" type="update" due-to="Dependabot">
         Update junit-jupiter from 5.6.2 to 5.7.0 #153.
diff --git a/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java b/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java
index b829dfc..70f92a2 100644
--- a/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java
+++ b/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java
@@ -30,8 +30,7 @@ public abstract class AbstractCharacterFilterReader extends FilterReader {
     /**
      * Constructs a new reader.
      *
-     * @param reader
-     *            the reader to filter
+     * @param reader the reader to filter
      */
     protected AbstractCharacterFilterReader(final Reader reader) {
         super(reader);
@@ -42,15 +41,14 @@ public abstract class AbstractCharacterFilterReader extends FilterReader {
         int ch;
         do {
             ch = in.read();
-        } while (filter(ch));
+        } while (ch != EOF && filter(ch));
         return ch;
     }
 
     /**
      * Returns true if the given character should be filtered out, false to keep the character.
      *
-     * @param ch
-     *            the character to test.
+     * @param ch the character to test.
      * @return true if the given character should be filtered out, false to keep the character.
      */
     protected abstract boolean filter(int ch);
diff --git a/src/main/java/org/apache/commons/io/input/CharacterSetFilterReader.java b/src/main/java/org/apache/commons/io/input/CharacterSetFilterReader.java
index 1016ed2..064cab6 100644
--- a/src/main/java/org/apache/commons/io/input/CharacterSetFilterReader.java
+++ b/src/main/java/org/apache/commons/io/input/CharacterSetFilterReader.java
@@ -17,15 +17,17 @@
 package org.apache.commons.io.input;
 
 import java.io.Reader;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 
 /**
- * A filter reader that removes a given set of characters represented as {@code int} code points, handy to remove
- * known junk characters from CSV files for example.
+ * A filter reader that removes a given set of characters represented as {@code int} code points, handy to remove known
+ * junk characters from CSV files for example.
  * <p>
- * This class must convert each {@code int} read to an {@code Integer}. You can increase the Integer cache
- * with a system property, see {@link Integer}.
+ * This class must convert each {@code int} read to an {@code Integer}. You can increase the Integer cache with a system
+ * property, see {@link Integer}.
  * </p>
  */
 public class CharacterSetFilterReader extends AbstractCharacterFilterReader {
@@ -36,10 +38,19 @@ public class CharacterSetFilterReader extends AbstractCharacterFilterReader {
     /**
      * Constructs a new reader.
      *
-     * @param reader
-     *            the reader to filter.
-     * @param skip
-     *            the set of characters to filter out.
+     * @param reader the reader to filter.
+     * @param skip the set of characters to filter out.
+     * @since 2.9.0
+     */
+    public CharacterSetFilterReader(final Reader reader, final Integer... skip) {
+        this(reader, new HashSet<>(Arrays.asList(skip)));
+    }
+
+    /**
+     * Constructs a new reader.
+     *
+     * @param reader the reader to filter.
+     * @param skip the set of characters to filter out.
      */
     public CharacterSetFilterReader(final Reader reader, final Set<Integer> skip) {
         super(reader);
diff --git a/src/test/java/org/apache/commons/io/input/CharacterFilterReaderTest.java b/src/test/java/org/apache/commons/io/input/CharacterFilterReaderTest.java
index 80a4f45..c2efad7 100644
--- a/src/test/java/org/apache/commons/io/input/CharacterFilterReaderTest.java
+++ b/src/test/java/org/apache/commons/io/input/CharacterFilterReaderTest.java
@@ -16,10 +16,13 @@
  */
 package org.apache.commons.io.input;
 
+import static org.apache.commons.io.IOUtils.EOF;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.time.Duration;
 import java.util.HashSet;
 
 import org.apache.commons.io.IOUtils;
@@ -28,6 +31,8 @@ import org.junit.jupiter.api.Test;
 
 public class CharacterFilterReaderTest {
 
+    private static final String STRING_FIXTURE = "ababcabcd";
+
     @Test
     public void testInputSize0FilterSize1() throws IOException {
         final StringReader input = new StringReader("");
@@ -41,7 +46,7 @@ public class CharacterFilterReaderTest {
     @Test
     public void testInputSize1FilterSize1() throws IOException {
         try (StringReader input = new StringReader("a");
-                CharacterFilterReader reader = new CharacterFilterReader(input, 'a')) {
+            CharacterFilterReader reader = new CharacterFilterReader(input, 'a')) {
             assertEquals(-1, reader.read());
         }
     }
@@ -73,8 +78,23 @@ public class CharacterFilterReaderTest {
     }
 
     @Test
+    public void testReadFilteringEOF() throws IOException {
+        final StringReader input = new StringReader(STRING_FIXTURE);
+        assertTimeoutPreemptively(Duration.ofMillis(500), () -> {
+            try (StringBuilderWriter output = new StringBuilderWriter();
+                CharacterFilterReader reader = new CharacterFilterReader(input, EOF)) {
+                int c;
+                while ((c = reader.read()) != EOF) {
+                    output.write(c);
+                }
+                assertEquals(STRING_FIXTURE, output.toString());
+            }
+        });
+    }
+
+    @Test
     public void testReadIntoBuffer() throws IOException {
-        final StringReader input = new StringReader("ababcabcd");
+        final StringReader input = new StringReader(STRING_FIXTURE);
         try (CharacterFilterReader reader = new CharacterFilterReader(input, 'b')) {
             final char[] buff = new char[9];
             final int charCount = reader.read(buff);
@@ -85,8 +105,9 @@ public class CharacterFilterReaderTest {
 
     @Test
     public void testReadUsingReader() throws IOException {
-        final StringReader input = new StringReader("ababcabcd");
-        try (StringBuilderWriter output = new StringBuilderWriter(); CharacterFilterReader reader = new CharacterFilterReader(input, 'b')) {
+        final StringReader input = new StringReader(STRING_FIXTURE);
+        try (StringBuilderWriter output = new StringBuilderWriter();
+            CharacterFilterReader reader = new CharacterFilterReader(input, 'b')) {
             IOUtils.copy(reader, output);
             assertEquals("aacacd", output.toString());
         }
diff --git a/src/test/java/org/apache/commons/io/input/CharacterSetFilterReaderTest.java b/src/test/java/org/apache/commons/io/input/CharacterSetFilterReaderTest.java
index 680021b..925491a 100644
--- a/src/test/java/org/apache/commons/io/input/CharacterSetFilterReaderTest.java
+++ b/src/test/java/org/apache/commons/io/input/CharacterSetFilterReaderTest.java
@@ -16,16 +16,23 @@
  */
 package org.apache.commons.io.input;
 
+import static org.apache.commons.io.IOUtils.EOF;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.time.Duration;
 import java.util.HashSet;
+import java.util.Set;
 
+import org.apache.commons.io.output.StringBuilderWriter;
 import org.junit.jupiter.api.Test;
 
 public class CharacterSetFilterReaderTest {
 
+    private static final String STRING_FIXTURE = "ab";
+
     @Test
     public void testInputSize0FilterSize0() throws IOException {
         final StringReader input = new StringReader("");
@@ -47,7 +54,7 @@ public class CharacterSetFilterReaderTest {
     @Test
     public void testInputSize0NullFilter() throws IOException {
         final StringReader input = new StringReader("");
-        try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, null)) {
+        try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, (Set<Integer>) null)) {
             assertEquals(-1, reader.read());
         }
     }
@@ -75,7 +82,7 @@ public class CharacterSetFilterReaderTest {
 
     @Test
     public void testInputSize2FilterSize1FilterFirst() throws IOException {
-        final StringReader input = new StringReader("ab");
+        final StringReader input = new StringReader(STRING_FIXTURE);
         final HashSet<Integer> codePoints = new HashSet<>();
         codePoints.add(Integer.valueOf('a'));
         try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
@@ -86,7 +93,7 @@ public class CharacterSetFilterReaderTest {
 
     @Test
     public void testInputSize2FilterSize1FilterLast() throws IOException {
-        final StringReader input = new StringReader("ab");
+        final StringReader input = new StringReader(STRING_FIXTURE);
         final HashSet<Integer> codePoints = new HashSet<>();
         codePoints.add(Integer.valueOf('b'));
         try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
@@ -97,7 +104,7 @@ public class CharacterSetFilterReaderTest {
 
     @Test
     public void testInputSize2FilterSize2FilterFirst() throws IOException {
-        final StringReader input = new StringReader("ab");
+        final StringReader input = new StringReader(STRING_FIXTURE);
         final HashSet<Integer> codePoints = new HashSet<>();
         codePoints.add(Integer.valueOf('a'));
         codePoints.add(Integer.valueOf('y'));
@@ -109,7 +116,7 @@ public class CharacterSetFilterReaderTest {
 
     @Test
     public void testInputSize2FilterSize2FilterLast() throws IOException {
-        final StringReader input = new StringReader("ab");
+        final StringReader input = new StringReader(STRING_FIXTURE);
         final HashSet<Integer> codePoints = new HashSet<>();
         codePoints.add(Integer.valueOf('x'));
         codePoints.add(Integer.valueOf('b'));
@@ -121,7 +128,7 @@ public class CharacterSetFilterReaderTest {
 
     @Test
     public void testInputSize2FilterSize2FilterNone() throws IOException {
-        final StringReader input = new StringReader("ab");
+        final StringReader input = new StringReader(STRING_FIXTURE);
         final HashSet<Integer> codePoints = new HashSet<>();
         codePoints.add(Integer.valueOf('x'));
         codePoints.add(Integer.valueOf('y'));
@@ -130,4 +137,19 @@ public class CharacterSetFilterReaderTest {
             assertEquals('b', reader.read());
         }
     }
+
+    @Test
+    public void testReadFilteringEOF() throws IOException {
+        final StringReader input = new StringReader(STRING_FIXTURE);
+        assertTimeoutPreemptively(Duration.ofMillis(500), () -> {
+            try (StringBuilderWriter output = new StringBuilderWriter();
+                CharacterSetFilterReader reader = new CharacterSetFilterReader(input, EOF)) {
+                int c;
+                while ((c = reader.read()) != EOF) {
+                    output.write(c);
+                }
+                assertEquals(STRING_FIXTURE, output.toString());
+            }
+        });
+    }
 }