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
pull/7262/head
Andy Wilkinson 8 years ago committed by Phillip Webb
parent 57d5a2ebc6
commit cdcc3d2f28

@ -371,6 +371,10 @@ public class JarFile extends java.util.jar.JarFile {
return this.pathFromRoot; return this.pathFromRoot;
} }
JarFileType getType() {
return this.type;
}
/** /**
* Register a {@literal 'java.protocol.handler.pkgs'} property so that a * Register a {@literal 'java.protocol.handler.pkgs'} property so that a
* {@link URLStreamHandler} will be located to deal with jar URLs. * {@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 DIRECT, NESTED_DIRECTORY, NESTED_JAR
} }

@ -29,6 +29,8 @@ import java.net.URLEncoder;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.security.Permission; import java.security.Permission;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
/** /**
* {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. * {@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) { if (this.jarFile == null) {
throw FILE_NOT_FOUND_EXCEPTION; 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"); throw new IOException("no entry name specified");
} }
connect(); 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) { if (inputStream == null) {
throwFileNotFound(this.jarEntryName, this.jarFile); throwFileNotFound(this.jarEntryName, this.jarFile);
} }

@ -28,6 +28,7 @@ import java.net.URLClassLoader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify;
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JarFileTests { public class JarFileTests {
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader"; private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";
@ -270,6 +272,12 @@ public class JarFileTests {
assertThat(conn.getJarFile()).isSameAs(nestedJarFile); assertThat(conn.getJarFile()).isSameAs(nestedJarFile);
assertThat(conn.getJarFileURL().toString()) assertThat(conn.getJarFileURL().toString())
.isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); .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); assertThat(conn.getPermission()).isInstanceOf(FilePermission.class);
FilePermission permission = (FilePermission) conn.getPermission(); FilePermission permission = (FilePermission) conn.getPermission();
assertThat(permission.getActions()).isEqualTo("read"); assertThat(permission.getActions()).isEqualTo("read");

Loading…
Cancel
Save