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 <gardgreg...@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()); + } + }); + } }