Commit 3d6c8a85 authored by Phillip Webb's avatar Phillip Webb

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
parent 5de2661b
...@@ -83,7 +83,7 @@ public class Handler extends URLStreamHandler { ...@@ -83,7 +83,7 @@ public class Handler extends URLStreamHandler {
return new JarURLConnection(url, this.jarFile); return new JarURLConnection(url, this.jarFile);
} }
try { try {
return new JarURLConnection(url, getJarFileFromUrl(url)); return new JarURLConnection(url, getRootJarFileFromUrl(url));
} }
catch (Exception ex) { catch (Exception ex) {
return openFallbackConnection(url, ex); return openFallbackConnection(url, ex);
...@@ -135,24 +135,14 @@ public class Handler extends URLStreamHandler { ...@@ -135,24 +135,14 @@ public class Handler extends URLStreamHandler {
return (URLConnection) OPEN_CONNECTION_METHOD.invoke(handler, url); 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(); String spec = url.getFile();
int separatorIndex = spec.indexOf(SEPARATOR); int separatorIndex = spec.indexOf(SEPARATOR);
if (separatorIndex == -1) { if (separatorIndex == -1) {
throw new MalformedURLException("Jar URL does not contain !/ separator"); throw new MalformedURLException("Jar URL does not contain !/ separator");
} }
String name = spec.substring(0, separatorIndex);
JarFile jar = null; return getRootJarFile(name);
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;
} }
private JarFile getRootJarFile(String name) throws IOException { private JarFile getRootJarFile(String name) throws IOException {
...@@ -175,15 +165,6 @@ public class Handler extends URLStreamHandler { ...@@ -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. * Add the given {@link JarFile} to the root file cache.
* @param sourceFile the source file to add * @param sourceFile the source file to add
......
...@@ -61,8 +61,6 @@ class JarURLConnection extends java.net.JarURLConnection { ...@@ -61,8 +61,6 @@ class JarURLConnection extends java.net.JarURLConnection {
private static ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<Boolean>(); private static ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<Boolean>();
private final String jarFileUrlSpec;
private final JarFile jarFile; private final JarFile jarFile;
private JarEntryData jarEntryData; private JarEntryData jarEntryData;
...@@ -71,19 +69,26 @@ class JarURLConnection extends java.net.JarURLConnection { ...@@ -71,19 +69,26 @@ class JarURLConnection extends java.net.JarURLConnection {
private JarEntryName jarEntryName; 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 // What we pass to super is ultimately ignored
super(EMPTY_JAR_URL); super(EMPTY_JAR_URL);
this.url = 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; this.jarFile = jarFile;
String spec = url.getFile(); this.jarEntryName = getJarEntryName(spec);
int separator = spec.lastIndexOf(SEPARATOR); }
if (separator == -1) {
throw new MalformedURLException("no " + SEPARATOR + " found in url spec:" private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
+ spec); JarEntry jarEntry = jarFile.getJarEntry(name);
if (jarEntry == null) {
throwFileNotFound(jarEntry, jarFile);
} }
this.jarFileUrlSpec = spec.substring(0, separator); return jarFile.getNestedJarFile(jarEntry);
this.jarEntryName = getJarEntryName(spec.substring(separator + 2));
} }
private JarEntryName getJarEntryName(String spec) { private JarEntryName getJarEntryName(String spec) {
...@@ -99,16 +104,20 @@ class JarURLConnection extends java.net.JarURLConnection { ...@@ -99,16 +104,20 @@ class JarURLConnection extends java.net.JarURLConnection {
this.jarEntryData = this.jarFile.getJarEntryData(this.jarEntryName this.jarEntryData = this.jarFile.getJarEntryData(this.jarEntryName
.asAsciiBytes()); .asAsciiBytes());
if (this.jarEntryData == null) { if (this.jarEntryData == null) {
if (Boolean.TRUE.equals(useFastExceptions.get())) { throwFileNotFound(this.jarEntryName, this.jarFile);
throw FILE_NOT_FOUND_EXCEPTION;
}
throw new FileNotFoundException("JAR entry " + this.jarEntryName
+ " not found in " + this.jarFile.getName());
} }
} }
this.connected = true; 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 @Override
public Manifest getManifest() throws IOException { public Manifest getManifest() throws IOException {
try { try {
...@@ -135,10 +144,14 @@ class JarURLConnection extends java.net.JarURLConnection { ...@@ -135,10 +144,14 @@ class JarURLConnection extends java.net.JarURLConnection {
private URL buildJarFileUrl() { private URL buildJarFileUrl() {
try { try {
if (this.jarFileUrlSpec.indexOf(SEPARATOR) == -1) { String spec = this.jarFile.getUrl().getFile();
return new URL(this.jarFileUrlSpec); 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) { catch (MalformedURLException ex) {
throw new IllegalStateException(ex); throw new IllegalStateException(ex);
......
...@@ -16,21 +16,32 @@ ...@@ -16,21 +16,32 @@
package org.springframework.boot.loader; package org.springframework.boot.loader;
import java.io.File;
import java.net.URL; import java.net.URL;
import org.junit.Rule;
import org.junit.Test; 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.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link LaunchedURLClassLoader}. * Tests for {@link LaunchedURLClassLoader}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
@SuppressWarnings("resource")
public class LaunchedURLClassLoaderTests { public class LaunchedURLClassLoaderTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test @Test
public void resolveResourceFromWindowsFilesystem() throws Exception { public void resolveResourceFromWindowsFilesystem() throws Exception {
// This path is invalid - it should return null even on Windows. // This path is invalid - it should return null even on Windows.
...@@ -76,4 +87,17 @@ public class LaunchedURLClassLoaderTests { ...@@ -76,4 +87,17 @@ public class LaunchedURLClassLoaderTests {
assertTrue(loader.getResources("").hasMoreElements()); 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));
}
} }
...@@ -408,4 +408,16 @@ public class JarFileTests { ...@@ -408,4 +408,16 @@ public class JarFileTests {
getEntries(); getEntries();
getNestedJarFile(); 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();
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment