Skip to content

Commit ed7a5db

Browse files
committed
Fail operations when JarFile is closed
Update `JarFile` to track when the instance has been closed and throw an exception in the same way that `ZipFile` does. Closes gh-21072
1 parent cc33e23 commit ed7a5db

File tree

3 files changed

+112
-5
lines changed

3 files changed

+112
-5
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@
2626
import java.net.URLStreamHandlerFactory;
2727
import java.util.Enumeration;
2828
import java.util.Iterator;
29+
import java.util.Spliterator;
30+
import java.util.Spliterators;
2931
import java.util.function.Supplier;
3032
import java.util.jar.JarInputStream;
3133
import java.util.jar.Manifest;
34+
import java.util.stream.Stream;
35+
import java.util.stream.StreamSupport;
3236
import java.util.zip.ZipEntry;
3337

3438
import org.springframework.boot.loader.data.RandomAccessData;
@@ -84,6 +88,8 @@ public class JarFile extends java.util.jar.JarFile implements Iterable<java.util
8488

8589
private String comment;
8690

91+
private volatile boolean closed;
92+
8793
/**
8894
* Create a new {@link JarFile} backed by the specified file.
8995
* @param file the root jar file
@@ -222,6 +228,13 @@ public Enumeration<java.util.jar.JarEntry> entries() {
222228
return new JarEntryEnumeration(this.entries.iterator());
223229
}
224230

231+
@Override
232+
public Stream<java.util.jar.JarEntry> stream() {
233+
Spliterator<java.util.jar.JarEntry> spliterator = Spliterators.spliterator(iterator(), size(),
234+
Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL);
235+
return StreamSupport.stream(spliterator, false);
236+
}
237+
225238
/**
226239
* Return an iterator for the contained entries.
227240
* @see java.lang.Iterable#iterator()
@@ -230,7 +243,7 @@ public Enumeration<java.util.jar.JarEntry> entries() {
230243
@Override
231244
@SuppressWarnings({ "unchecked", "rawtypes" })
232245
public Iterator<java.util.jar.JarEntry> iterator() {
233-
return (Iterator) this.entries.iterator();
246+
return (Iterator) this.entries.iterator(this::ensureOpen);
234247
}
235248

236249
public JarEntry getJarEntry(CharSequence name) {
@@ -248,11 +261,13 @@ public boolean containsEntry(String name) {
248261

249262
@Override
250263
public ZipEntry getEntry(String name) {
264+
ensureOpen();
251265
return this.entries.getEntry(name);
252266
}
253267

254268
@Override
255269
public synchronized InputStream getInputStream(ZipEntry entry) throws IOException {
270+
ensureOpen();
256271
if (entry instanceof JarEntry) {
257272
return this.entries.getInputStream((JarEntry) entry);
258273
}
@@ -322,22 +337,34 @@ private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException {
322337

323338
@Override
324339
public String getComment() {
340+
ensureOpen();
325341
return this.comment;
326342
}
327343

328344
@Override
329345
public int size() {
346+
ensureOpen();
330347
return this.entries.getSize();
331348
}
332349

333350
@Override
334351
public void close() throws IOException {
352+
if (this.closed) {
353+
return;
354+
}
355+
this.closed = true;
335356
super.close();
336357
if (this.type == JarFileType.DIRECT && this.parent == null) {
337358
this.rootFile.close();
338359
}
339360
}
340361

362+
private void ensureOpen() {
363+
if (this.closed) {
364+
throw new IllegalStateException("zip file closed");
365+
}
366+
}
367+
341368
String getUrlString() throws MalformedURLException {
342369
if (this.urlString == null) {
343370
this.urlString = getUrl().toString();

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,6 +47,9 @@
4747
*/
4848
class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
4949

50+
private static final Runnable NO_VALIDATION = () -> {
51+
};
52+
5053
private static final String META_INF_PREFIX = "META-INF/";
5154

5255
private static final Name MULTI_RELEASE = new Name("Multi-Release");
@@ -192,7 +195,11 @@ private void swap(int[] array, int i, int j) {
192195

193196
@Override
194197
public Iterator<JarEntry> iterator() {
195-
return new EntryIterator();
198+
return new EntryIterator(NO_VALIDATION);
199+
}
200+
201+
Iterator<JarEntry> iterator(Runnable validator) {
202+
return new EntryIterator(validator);
196203
}
197204

198205
boolean containsEntry(CharSequence name) {
@@ -347,17 +354,26 @@ private AsciiBytes applyFilter(AsciiBytes name) {
347354
/**
348355
* Iterator for contained entries.
349356
*/
350-
private class EntryIterator implements Iterator<JarEntry> {
357+
private final class EntryIterator implements Iterator<JarEntry> {
358+
359+
private final Runnable validator;
351360

352361
private int index = 0;
353362

363+
private EntryIterator(Runnable validator) {
364+
this.validator = validator;
365+
validator.run();
366+
}
367+
354368
@Override
355369
public boolean hasNext() {
370+
this.validator.run();
356371
return this.index < JarFileEntries.this.size;
357372
}
358373

359374
@Override
360375
public JarEntry next() {
376+
this.validator.run();
361377
if (!hasNext()) {
362378
throw new NoSuchElementException();
363379
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,21 @@
3030
import java.nio.charset.StandardCharsets;
3131
import java.nio.file.attribute.FileTime;
3232
import java.time.Instant;
33+
import java.util.ArrayList;
3334
import java.util.Collections;
3435
import java.util.Enumeration;
36+
import java.util.Iterator;
3537
import java.util.List;
3638
import java.util.jar.JarEntry;
3739
import java.util.jar.JarInputStream;
3840
import java.util.jar.JarOutputStream;
3941
import java.util.jar.Manifest;
42+
import java.util.stream.Stream;
4043
import java.util.zip.CRC32;
4144
import java.util.zip.ZipEntry;
4245
import java.util.zip.ZipFile;
4346

47+
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
4448
import org.junit.jupiter.api.AfterEach;
4549
import org.junit.jupiter.api.BeforeEach;
4650
import org.junit.jupiter.api.Test;
@@ -56,6 +60,7 @@
5660
import static org.assertj.core.api.Assertions.assertThat;
5761
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5862
import static org.assertj.core.api.Assertions.assertThatIOException;
63+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
5964
import static org.mockito.Mockito.spy;
6065
import static org.mockito.Mockito.verify;
6166

@@ -171,6 +176,12 @@ void getJarEntry() {
171176
assertThat(entry.getName()).isEqualTo("1.dat");
172177
}
173178

179+
@Test
180+
void getJarEntryWhenClosed() throws Exception {
181+
this.jarFile.close();
182+
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getJarEntry("1.dat"));
183+
}
184+
174185
@Test
175186
void getInputStream() throws Exception {
176187
InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("1.dat"));
@@ -180,23 +191,41 @@ void getInputStream() throws Exception {
180191
assertThat(inputStream.read()).isEqualTo(-1);
181192
}
182193

194+
@Test
195+
void getInputStreamWhenClosed() throws Exception {
196+
this.jarFile.close();
197+
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getInputStream(this.jarFile.getEntry("1.dat")));
198+
}
199+
183200
@Test
184201
void getComment() {
185202
assertThat(this.jarFile.getComment()).isEqualTo("outer");
186203
}
187204

205+
@Test
206+
void getCommentWhenClosed() throws Exception {
207+
this.jarFile.close();
208+
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getComment());
209+
}
210+
188211
@Test
189212
void getName() {
190213
assertThat(this.jarFile.getName()).isEqualTo(this.rootJarFile.getPath());
191214
}
192215

193216
@Test
194-
void getSize() throws Exception {
217+
void size() throws Exception {
195218
try (ZipFile zip = new ZipFile(this.rootJarFile)) {
196219
assertThat(this.jarFile.size()).isEqualTo(zip.size());
197220
}
198221
}
199222

223+
@Test
224+
void sizeWhenClosed() throws Exception {
225+
this.jarFile.close();
226+
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.size());
227+
}
228+
200229
@Test
201230
void getEntryTime() throws Exception {
202231
java.util.jar.JarFile jdkJarFile = new java.util.jar.JarFile(this.rootJarFile);
@@ -603,6 +632,41 @@ void jarFileEntryWithEpochTimeOfZeroShouldNotFail() throws Exception {
603632
}
604633
}
605634

635+
@Test
636+
void iterator() {
637+
Iterator<JarEntry> iterator = this.jarFile.iterator();
638+
List<String> names = new ArrayList<String>();
639+
while (iterator.hasNext()) {
640+
names.add(iterator.next().getName());
641+
}
642+
assertThat(names).hasSize(12).contains("1.dat");
643+
}
644+
645+
@Test
646+
void iteratorWhenClosed() throws IOException {
647+
this.jarFile.close();
648+
assertThatZipFileClosedIsThrownBy(() -> this.jarFile.iterator());
649+
}
650+
651+
@Test
652+
void iteratorWhenClosedLater() throws IOException {
653+
Iterator<JarEntry> iterator = this.jarFile.iterator();
654+
iterator.next();
655+
this.jarFile.close();
656+
assertThatZipFileClosedIsThrownBy(() -> iterator.hasNext());
657+
}
658+
659+
@Test
660+
void stream() {
661+
Stream<String> stream = this.jarFile.stream().map(JarEntry::getName);
662+
assertThat(stream).hasSize(12).contains("1.dat");
663+
664+
}
665+
666+
private void assertThatZipFileClosedIsThrownBy(ThrowingCallable throwingCallable) {
667+
assertThatIllegalStateException().isThrownBy(throwingCallable).withMessage("zip file closed");
668+
}
669+
606670
private int getJavaVersion() {
607671
try {
608672
Object runtimeVersion = Runtime.class.getMethod("version").invoke(null);

0 commit comments

Comments
 (0)