From cdcc3d2f28ff4bc3687b6c186e8060fa2441ebd6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 23 Sep 2016 15:26:07 +0100 Subject: [PATCH] Ensure getResources("") includes nested root URLs Previously, if Boot's JarURLConnection pointed to the root of a nested entry, e.g. /BOOT-INF/classes, a call to getInputStream() would throw an IOException. This behavior is reasonable for a URL that points to the root of a normal jar as the jar itself is on the class path anyway. However, for a nested jar it meant that a call to ClassLoader.getResources("") would not include URLs for any nested jars and directories (/BOOT-INF/classes and jars in /BOOT-INF/lib). This is due to some logic in URLClassPath.Loader.findResource that verifies a URL by opening a connection and calling getInputStream(). The result of missing URLs for the root of nested jars and directories is that classpath scanning that scans from the root (not a good idea for performance reasons, but something that we should support) would not find entries in /BOOT-INF/classes or in jars in /BOOT-INF/lib. This commit updates our JarURLConnection so that it no longer throws an IOException when asked for an InputStream for the root of a nested entry (directory or jar). Fixes gh-7003 --- .../org/springframework/boot/loader/jar/JarFile.java | 9 ++++++++- .../boot/loader/jar/JarURLConnection.java | 9 +++++++-- .../springframework/boot/loader/jar/JarFileTests.java | 8 ++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java index 7a3ee76a4ff..162395c295b 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java @@ -371,6 +371,10 @@ public class JarFile extends java.util.jar.JarFile { return this.pathFromRoot; } + JarFileType getType() { + return this.type; + } + /** * Register a {@literal 'java.protocol.handler.pkgs'} property so that a * {@link URLStreamHandler} will be located to deal with jar URLs. @@ -396,7 +400,10 @@ public class JarFile extends java.util.jar.JarFile { } } - private enum JarFileType { + /** + * The type of a {@link JarFile}. + */ + enum JarFileType { DIRECT, NESTED_DIRECTORY, NESTED_JAR } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java index ca2ff46c16a..7f2b5ce6ed2 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java @@ -29,6 +29,8 @@ import java.net.URLEncoder; import java.net.URLStreamHandler; import java.security.Permission; +import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess; + /** * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. * @@ -160,11 +162,14 @@ final class JarURLConnection extends java.net.JarURLConnection { if (this.jarFile == null) { throw FILE_NOT_FOUND_EXCEPTION; } - if (this.jarEntryName.isEmpty()) { + if (this.jarEntryName.isEmpty() + && this.jarFile.getType() == JarFile.JarFileType.DIRECT) { throw new IOException("no entry name specified"); } connect(); - InputStream inputStream = this.jarFile.getInputStream(this.jarEntry); + InputStream inputStream = (this.jarEntryName.isEmpty() + ? this.jarFile.getData().getInputStream(ResourceAccess.ONCE) + : this.jarFile.getInputStream(this.jarEntry)); if (inputStream == null) { throwFileNotFound(this.jarEntryName, this.jarFile); } diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java index f4cc67b9ff8..d9e29934004 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java @@ -28,6 +28,7 @@ import java.net.URLClassLoader; import java.nio.charset.Charset; import java.util.Enumeration; import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; @@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify; * @author Andy Wilkinson */ public class JarFileTests { + private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader"; @@ -270,6 +272,12 @@ public class JarFileTests { assertThat(conn.getJarFile()).isSameAs(nestedJarFile); assertThat(conn.getJarFileURL().toString()) .isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); + assertThat(conn.getInputStream()).isNotNull(); + JarInputStream jarInputStream = new JarInputStream(conn.getInputStream()); + assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("3.dat"); + assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("4.dat"); + assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("\u00E4.dat"); + jarInputStream.close(); assertThat(conn.getPermission()).isInstanceOf(FilePermission.class); FilePermission permission = (FilePermission) conn.getPermission(); assertThat(permission.getActions()).isEqualTo("read");