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
This commit is contained in:
Phillip Webb 2014-06-22 11:29:29 -07:00
parent 5de2661b43
commit 3d6c8a85f4
4 changed files with 72 additions and 42 deletions

View File

@ -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

View File

@ -61,8 +61,6 @@ class JarURLConnection extends java.net.JarURLConnection {
private static ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<Boolean>();
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;
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);
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.jarFileUrlSpec = spec.substring(0, separator);
this.jarEntryName = getJarEntryName(spec.substring(separator + 2));
this.jarFile = jarFile;
this.jarEntryName = getJarEntryName(spec);
}
private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
JarEntry jarEntry = jarFile.getJarEntry(name);
if (jarEntry == null) {
throwFileNotFound(jarEntry, jarFile);
}
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());
}
return new URL("jar:" + this.jarFileUrlSpec);
if (spec.indexOf(SEPARATOR) == -1) {
return new URL(spec);
}
return new URL("jar:" + spec);
}
catch (MalformedURLException ex) {
throw new IllegalStateException(ex);

View File

@ -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));
}
}

View File

@ -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();
}
}