From f28caee30d2ea48a0b19405dc604090e4b2f9b3d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 5 Sep 2025 17:14:54 +0100 Subject: [PATCH] Fix NestedJarFile.JarEntryInputStream's available() behavior Previously, available() would return 0 initially and then negative values once some data head been read. It should be a positive value (for entries with content) initially a decrease as data is read reaching zero once an entry's data has been read in its entirety. This commit initialises the count of the remaining bytes to be equal to the entry's uncompressed size. It also removes logic that closes the stream when remaining equals zero upon read or skip. This condition was not reached before as remaining would become negative as soon as any data was read or skipped. With the correct initialization of remaining, the condition is now reached and it results in test failures due to premature closure. Furthermore, the javadoc of read and skip do not require the stream to be closed when the reach end of file. Closes gh-47056 --- .../boot/loader/jar/NestedJarFile.java | 7 +-- .../boot/loader/jar/NestedJarFileTests.java | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/NestedJarFile.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/NestedJarFile.java index e12cc4c3efc..8e1f96b5e63 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/NestedJarFile.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/NestedJarFile.java @@ -706,6 +706,7 @@ public class NestedJarFile extends JarFile { JarEntryInputStream(ZipContent.Entry entry) throws IOException { this.uncompressedSize = entry.getUncompressedSize(); this.content = entry.openContent(); + this.remaining = this.uncompressedSize; } @Override @@ -727,9 +728,6 @@ public class NestedJarFile extends JarFile { } result = count; } - if (this.remaining == 0) { - close(); - } return result; } @@ -741,9 +739,6 @@ public class NestedJarFile extends JarFile { this.pos += result; this.remaining -= result; } - if (this.remaining == 0) { - close(); - } return result; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java index 989f2148dfb..fc35d91c690 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java @@ -431,6 +431,56 @@ class NestedJarFileTests { .hasMessage("Content mismatch when reading security info for entry 'content' (content check)"); } + @Test + void readingToEndOfStoredContentCausesAvailableToReachZero() throws IOException { + try (NestedJarFile jar = new NestedJarFile(this.file)) { + JarEntry entry = jar.getEntry("nested.jar"); + try (InputStream input = jar.getInputStream(entry)) { + assertThat(input.available()).isGreaterThan(0); + StreamUtils.copyToByteArray(input); + assertThat(input.available()).isZero(); + } + } + } + + @Test + void readingToEndOfDeflatedContentCausesAvailableToReachZero() throws IOException { + try (NestedJarFile jar = new NestedJarFile(this.file)) { + JarEntry entry = jar.getEntry("d/9.dat"); + try (InputStream input = jar.getInputStream(entry)) { + assertThat(input.available()).isGreaterThan(0); + StreamUtils.copyToByteArray(input); + assertThat(input.available()).isZero(); + } + } + } + + @Test + void skippingBeyondEndOfStoredContentCausesAvailableToReachZero() throws IOException { + try (NestedJarFile jar = new NestedJarFile(this.file)) { + JarEntry entry = jar.getEntry("nested.jar"); + try (InputStream input = jar.getInputStream(entry)) { + assertThat(input.available()).isGreaterThan(0); + long skipped = input.skip(1000); + assertThat(skipped).isLessThan(1000); + assertThat(input.available()).isZero(); + } + } + } + + @Test + void skippingBeyondEndOfDeflatedContentCausesAvailableToReachZero() throws IOException { + try (NestedJarFile jar = new NestedJarFile(this.file)) { + JarEntry entry = jar.getEntry("d/9.dat"); + try (InputStream input = jar.getInputStream(entry)) { + assertThat(input.available()).isGreaterThan(0); + long skipped = input.skip(1000); + assertThat(skipped).isLessThan(1000); + assertThat(input.available()).isZero(); + } + } + } + private List collectComments(JarFile jarFile) throws IOException { try (jarFile) { List comments = new ArrayList<>();