From 3d6c8a85f4d7e64e80a3c7c6ec0fa6750e23dc59 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sun, 22 Jun 2014 11:29:29 -0700 Subject: [PATCH] Allow URL resolution within nested JARs Update JarURLConnection to allow the resolution of items within a nested jar, even if the jarFile passed to the connection is several levels up. This prevent a connection from incorrectly resolving an entry against the wrong jar file. See gh-1070 --- .../boot/loader/jar/Handler.java | 27 ++-------- .../boot/loader/jar/JarURLConnection.java | 49 ++++++++++++------- .../loader/LaunchedURLClassLoaderTests.java | 24 +++++++++ .../boot/loader/jar/JarFileTests.java | 12 +++++ 4 files changed, 71 insertions(+), 41 deletions(-) diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java index 25569b2b5c..0a32e26ce0 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java @@ -83,7 +83,7 @@ public class Handler extends URLStreamHandler { return new JarURLConnection(url, this.jarFile); } try { - return new JarURLConnection(url, getJarFileFromUrl(url)); + return new JarURLConnection(url, getRootJarFileFromUrl(url)); } catch (Exception ex) { return openFallbackConnection(url, ex); @@ -135,24 +135,14 @@ public class Handler extends URLStreamHandler { return (URLConnection) OPEN_CONNECTION_METHOD.invoke(handler, url); } - public JarFile getJarFileFromUrl(URL url) throws IOException { - + public JarFile getRootJarFileFromUrl(URL url) throws IOException { String spec = url.getFile(); - int separatorIndex = spec.indexOf(SEPARATOR); if (separatorIndex == -1) { throw new MalformedURLException("Jar URL does not contain !/ separator"); } - - JarFile jar = null; - while (separatorIndex != -1) { - String name = spec.substring(0, separatorIndex); - jar = (jar == null ? getRootJarFile(name) : getNestedJarFile(jar, name)); - spec = spec.substring(separatorIndex + SEPARATOR.length()); - separatorIndex = spec.indexOf(SEPARATOR); - } - - return jar; + String name = spec.substring(0, separatorIndex); + return getRootJarFile(name); } private JarFile getRootJarFile(String name) throws IOException { @@ -175,15 +165,6 @@ public class Handler extends URLStreamHandler { } } - private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException { - JarEntry jarEntry = jarFile.getJarEntry(name); - if (jarEntry == null) { - throw new IOException("Unable to find nested jar '" + name + "' from '" - + jarFile + "'"); - } - return jarFile.getNestedJarFile(jarEntry); - } - /** * Add the given {@link JarFile} to the root file cache. * @param sourceFile the source file to add 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 e4320e3070..fb4c7acd38 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 @@ -61,8 +61,6 @@ class JarURLConnection extends java.net.JarURLConnection { private static ThreadLocal useFastExceptions = new ThreadLocal(); - private final String jarFileUrlSpec; - private final JarFile jarFile; private JarEntryData jarEntryData; @@ -71,19 +69,26 @@ class JarURLConnection extends java.net.JarURLConnection { private JarEntryName jarEntryName; - protected JarURLConnection(URL url, JarFile jarFile) throws MalformedURLException { + protected JarURLConnection(URL url, JarFile jarFile) throws IOException { // What we pass to super is ultimately ignored super(EMPTY_JAR_URL); this.url = url; + String spec = url.getFile().substring(jarFile.getUrl().getFile().length()); + int separator; + while ((separator = spec.indexOf(SEPARATOR)) > 0) { + jarFile = getNestedJarFile(jarFile, spec.substring(0, separator)); + spec = spec.substring(separator + SEPARATOR.length()); + } this.jarFile = jarFile; - String spec = url.getFile(); - int separator = spec.lastIndexOf(SEPARATOR); - if (separator == -1) { - throw new MalformedURLException("no " + SEPARATOR + " found in url spec:" - + spec); + this.jarEntryName = getJarEntryName(spec); + } + + private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException { + JarEntry jarEntry = jarFile.getJarEntry(name); + if (jarEntry == null) { + throwFileNotFound(jarEntry, jarFile); } - this.jarFileUrlSpec = spec.substring(0, separator); - this.jarEntryName = getJarEntryName(spec.substring(separator + 2)); + return jarFile.getNestedJarFile(jarEntry); } private JarEntryName getJarEntryName(String spec) { @@ -99,16 +104,20 @@ class JarURLConnection extends java.net.JarURLConnection { this.jarEntryData = this.jarFile.getJarEntryData(this.jarEntryName .asAsciiBytes()); if (this.jarEntryData == null) { - if (Boolean.TRUE.equals(useFastExceptions.get())) { - throw FILE_NOT_FOUND_EXCEPTION; - } - throw new FileNotFoundException("JAR entry " + this.jarEntryName - + " not found in " + this.jarFile.getName()); + throwFileNotFound(this.jarEntryName, this.jarFile); } } this.connected = true; } + private void throwFileNotFound(Object entry, JarFile jarFile) throws FileNotFoundException { + if (Boolean.TRUE.equals(useFastExceptions.get())) { + throw FILE_NOT_FOUND_EXCEPTION; + } + throw new FileNotFoundException("JAR entry " + entry + " not found in " + + jarFile.getName()); + } + @Override public Manifest getManifest() throws IOException { try { @@ -135,10 +144,14 @@ class JarURLConnection extends java.net.JarURLConnection { private URL buildJarFileUrl() { try { - if (this.jarFileUrlSpec.indexOf(SEPARATOR) == -1) { - return new URL(this.jarFileUrlSpec); + String spec = this.jarFile.getUrl().getFile(); + if (spec.endsWith(SEPARATOR)) { + spec = spec.substring(0, spec.length() - SEPARATOR.length()); + } + if (spec.indexOf(SEPARATOR) == -1) { + return new URL(spec); } - return new URL("jar:" + this.jarFileUrlSpec); + return new URL("jar:" + spec); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java index b453c5851c..2fa10b5d87 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java @@ -16,21 +16,32 @@ package org.springframework.boot.loader; +import java.io.File; import java.net.URL; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.boot.loader.jar.JarFile; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** * Tests for {@link LaunchedURLClassLoader}. * * @author Dave Syer + * @author Phillip Webb */ +@SuppressWarnings("resource") public class LaunchedURLClassLoaderTests { + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Test public void resolveResourceFromWindowsFilesystem() throws Exception { // This path is invalid - it should return null even on Windows. @@ -76,4 +87,17 @@ public class LaunchedURLClassLoaderTests { assertTrue(loader.getResources("").hasMoreElements()); } + @Test + public void resolveFromNested() throws Exception { + File file = this.temporaryFolder.newFile(); + TestJarCreator.createTestJar(file); + JarFile jarFile = new JarFile(file); + URL url = jarFile.getUrl(); + LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, + null); + URL resource = loader.getResource("nested.jar!/3.dat"); + assertThat(resource.toString(), equalTo(url + "nested.jar!/3.dat")); + assertThat(resource.openConnection().getInputStream().read(), equalTo(3)); + } + } 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 81edda650e..a133b7ee82 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 @@ -408,4 +408,16 @@ public class JarFileTests { getEntries(); getNestedJarFile(); } + + @Test + public void cannotLoadMissingJar() throws Exception { + // relates to gh-1070 + JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile + .getEntry("nested.jar")); + URL nestedUrl = nestedJarFile.getUrl(); + URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat"); + this.thrown.expect(FileNotFoundException.class); + url.openConnection().getInputStream(); + } + }