diff --git a/src/main/java/org/apache/commons/csv/CSVParser.java b/src/main/java/org/apache/commons/csv/CSVParser.java index 891539e86..0b3a96c96 100644 --- a/src/main/java/org/apache/commons/csv/CSVParser.java +++ b/src/main/java/org/apache/commons/csv/CSVParser.java @@ -50,6 +50,7 @@ import java.util.stream.StreamSupport; import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.build.AbstractStreamBuilder; import org.apache.commons.io.function.Uncheck; @@ -389,10 +390,16 @@ public static CSVParser parse(final InputStream inputStream, final Charset chars * @throws NullPointerException if {@code path} is {@code null}. * @since 1.5 */ - @SuppressWarnings("resource") public static CSVParser parse(final Path path, final Charset charset, final CSVFormat format) throws IOException { Objects.requireNonNull(path, "path"); - return parse(Files.newInputStream(path), charset, format); + final InputStream inputStream = Files.newInputStream(path); + try { + return parse(inputStream, charset, format); + } catch (final IOException | RuntimeException e) { + // This method allocated the stream and the caller never gets a parser to close, so close it here. + IOUtils.closeQuietlySuppress(inputStream, e); + throw e; + } } /** @@ -461,10 +468,16 @@ public static CSVParser parse(final String string, final CSVFormat format) throw * @throws CSVException Thrown on invalid CSV input data. * @throws NullPointerException if {@code url} is {@code null}. */ - @SuppressWarnings("resource") public static CSVParser parse(final URL url, final Charset charset, final CSVFormat format) throws IOException { Objects.requireNonNull(url, "url"); - return parse(url.openStream(), charset, format); + final InputStream inputStream = url.openStream(); + try { + return parse(inputStream, charset, format); + } catch (final IOException | RuntimeException e) { + // This method allocated the stream and the caller never gets a parser to close, so close it here. + IOUtils.closeQuietlySuppress(inputStream, e); + throw e; + } } private String headerComment; diff --git a/src/test/java/org/apache/commons/csv/CSVParserTest.java b/src/test/java/org/apache/commons/csv/CSVParserTest.java index a02041423..c8c7324a8 100644 --- a/src/test/java/org/apache/commons/csv/CSVParserTest.java +++ b/src/test/java/org/apache/commons/csv/CSVParserTest.java @@ -45,6 +45,8 @@ import java.io.StringWriter; import java.io.UncheckedIOException; import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -1667,6 +1669,18 @@ void testParsePathCharsetNullFormat() throws IOException { } } + @Test + void testParsePathClosesInputStreamWhenHeaderIsInvalid() throws IOException { + final Path path = Files.createTempFile(getClass().getName(), ".csv"); + try { + Files.write(path, "A,,C\n1,2,3\n".getBytes(UTF_8)); + final CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().get(); + assertThrows(IllegalArgumentException.class, () -> CSVParser.parse(path, UTF_8, format)); + } finally { + Files.delete(path); + } + } + @Test void testParserUrlNullCharsetFormat() throws IOException { final URL url = ClassLoader.getSystemClassLoader().getResource("org/apache/commons/csv/CSVFileParser/test.csv"); @@ -1699,6 +1713,40 @@ void testParseUrlCharsetNullFormat() throws IOException { } } + @Test + void testParseUrlClosesInputStreamWhenHeaderIsInvalid() throws IOException { + final AtomicBoolean closed = new AtomicBoolean(); + final URLStreamHandler handler = new URLStreamHandler() { + + @Override + protected URLConnection openConnection(final URL u) { + return new URLConnection(u) { + + @Override + public void connect() { + // noop + } + + @Override + public InputStream getInputStream() { + return new FilterInputStream(new ByteArrayInputStream("A,,C\n1,2,3\n".getBytes(UTF_8))) { + + @Override + public void close() throws IOException { + closed.set(true); + super.close(); + } + }; + } + }; + } + }; + final URL url = new URL("csv", null, -1, "test.csv", handler); + final CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().get(); + assertThrows(IllegalArgumentException.class, () -> CSVParser.parse(url, UTF_8, format)); + assertTrue(closed.get(), "The stream opened from the URL must be closed when the parser cannot be constructed"); + } + @Test void testParseWithDelimiterStringFromChunkedReader() throws IOException { final CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setEscape('!').get();