Skip to content

Commit 3d6c8a8

Browse files
author
Phillip Webb
committed
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
1 parent 5de2661 commit 3d6c8a8

File tree

4 files changed

+71
-41
lines changed

4 files changed

+71
-41
lines changed

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ protected URLConnection openConnection(URL url) throws IOException {
8383
return new JarURLConnection(url, this.jarFile);
8484
}
8585
try {
86-
return new JarURLConnection(url, getJarFileFromUrl(url));
86+
return new JarURLConnection(url, getRootJarFileFromUrl(url));
8787
}
8888
catch (Exception ex) {
8989
return openFallbackConnection(url, ex);
@@ -135,24 +135,14 @@ private URLConnection openConnection(URLStreamHandler handler, URL url)
135135
return (URLConnection) OPEN_CONNECTION_METHOD.invoke(handler, url);
136136
}
137137

138-
public JarFile getJarFileFromUrl(URL url) throws IOException {
139-
138+
public JarFile getRootJarFileFromUrl(URL url) throws IOException {
140139
String spec = url.getFile();
141-
142140
int separatorIndex = spec.indexOf(SEPARATOR);
143141
if (separatorIndex == -1) {
144142
throw new MalformedURLException("Jar URL does not contain !/ separator");
145143
}
146-
147-
JarFile jar = null;
148-
while (separatorIndex != -1) {
149-
String name = spec.substring(0, separatorIndex);
150-
jar = (jar == null ? getRootJarFile(name) : getNestedJarFile(jar, name));
151-
spec = spec.substring(separatorIndex + SEPARATOR.length());
152-
separatorIndex = spec.indexOf(SEPARATOR);
153-
}
154-
155-
return jar;
144+
String name = spec.substring(0, separatorIndex);
145+
return getRootJarFile(name);
156146
}
157147

158148
private JarFile getRootJarFile(String name) throws IOException {
@@ -175,15 +165,6 @@ private JarFile getRootJarFile(String name) throws IOException {
175165
}
176166
}
177167

178-
private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
179-
JarEntry jarEntry = jarFile.getJarEntry(name);
180-
if (jarEntry == null) {
181-
throw new IOException("Unable to find nested jar '" + name + "' from '"
182-
+ jarFile + "'");
183-
}
184-
return jarFile.getNestedJarFile(jarEntry);
185-
}
186-
187168
/**
188169
* Add the given {@link JarFile} to the root file cache.
189170
* @param sourceFile the source file to add

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ protected URLConnection openConnection(URL u) throws IOException {
6161

6262
private static ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<Boolean>();
6363

64-
private final String jarFileUrlSpec;
65-
6664
private final JarFile jarFile;
6765

6866
private JarEntryData jarEntryData;
@@ -71,19 +69,26 @@ protected URLConnection openConnection(URL u) throws IOException {
7169

7270
private JarEntryName jarEntryName;
7371

74-
protected JarURLConnection(URL url, JarFile jarFile) throws MalformedURLException {
72+
protected JarURLConnection(URL url, JarFile jarFile) throws IOException {
7573
// What we pass to super is ultimately ignored
7674
super(EMPTY_JAR_URL);
7775
this.url = url;
76+
String spec = url.getFile().substring(jarFile.getUrl().getFile().length());
77+
int separator;
78+
while ((separator = spec.indexOf(SEPARATOR)) > 0) {
79+
jarFile = getNestedJarFile(jarFile, spec.substring(0, separator));
80+
spec = spec.substring(separator + SEPARATOR.length());
81+
}
7882
this.jarFile = jarFile;
79-
String spec = url.getFile();
80-
int separator = spec.lastIndexOf(SEPARATOR);
81-
if (separator == -1) {
82-
throw new MalformedURLException("no " + SEPARATOR + " found in url spec:"
83-
+ spec);
83+
this.jarEntryName = getJarEntryName(spec);
84+
}
85+
86+
private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
87+
JarEntry jarEntry = jarFile.getJarEntry(name);
88+
if (jarEntry == null) {
89+
throwFileNotFound(jarEntry, jarFile);
8490
}
85-
this.jarFileUrlSpec = spec.substring(0, separator);
86-
this.jarEntryName = getJarEntryName(spec.substring(separator + 2));
91+
return jarFile.getNestedJarFile(jarEntry);
8792
}
8893

8994
private JarEntryName getJarEntryName(String spec) {
@@ -99,16 +104,20 @@ public void connect() throws IOException {
99104
this.jarEntryData = this.jarFile.getJarEntryData(this.jarEntryName
100105
.asAsciiBytes());
101106
if (this.jarEntryData == null) {
102-
if (Boolean.TRUE.equals(useFastExceptions.get())) {
103-
throw FILE_NOT_FOUND_EXCEPTION;
104-
}
105-
throw new FileNotFoundException("JAR entry " + this.jarEntryName
106-
+ " not found in " + this.jarFile.getName());
107+
throwFileNotFound(this.jarEntryName, this.jarFile);
107108
}
108109
}
109110
this.connected = true;
110111
}
111112

113+
private void throwFileNotFound(Object entry, JarFile jarFile) throws FileNotFoundException {
114+
if (Boolean.TRUE.equals(useFastExceptions.get())) {
115+
throw FILE_NOT_FOUND_EXCEPTION;
116+
}
117+
throw new FileNotFoundException("JAR entry " + entry + " not found in "
118+
+ jarFile.getName());
119+
}
120+
112121
@Override
113122
public Manifest getManifest() throws IOException {
114123
try {
@@ -135,10 +144,14 @@ public URL getJarFileURL() {
135144

136145
private URL buildJarFileUrl() {
137146
try {
138-
if (this.jarFileUrlSpec.indexOf(SEPARATOR) == -1) {
139-
return new URL(this.jarFileUrlSpec);
147+
String spec = this.jarFile.getUrl().getFile();
148+
if (spec.endsWith(SEPARATOR)) {
149+
spec = spec.substring(0, spec.length() - SEPARATOR.length());
150+
}
151+
if (spec.indexOf(SEPARATOR) == -1) {
152+
return new URL(spec);
140153
}
141-
return new URL("jar:" + this.jarFileUrlSpec);
154+
return new URL("jar:" + spec);
142155
}
143156
catch (MalformedURLException ex) {
144157
throw new IllegalStateException(ex);

spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,32 @@
1616

1717
package org.springframework.boot.loader;
1818

19+
import java.io.File;
1920
import java.net.URL;
2021

22+
import org.junit.Rule;
2123
import org.junit.Test;
24+
import org.junit.rules.TemporaryFolder;
25+
import org.springframework.boot.loader.jar.JarFile;
2226

27+
import static org.hamcrest.Matchers.equalTo;
2328
import static org.junit.Assert.assertNotNull;
2429
import static org.junit.Assert.assertNull;
30+
import static org.junit.Assert.assertThat;
2531
import static org.junit.Assert.assertTrue;
2632

2733
/**
2834
* Tests for {@link LaunchedURLClassLoader}.
2935
*
3036
* @author Dave Syer
37+
* @author Phillip Webb
3138
*/
39+
@SuppressWarnings("resource")
3240
public class LaunchedURLClassLoaderTests {
3341

42+
@Rule
43+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
44+
3445
@Test
3546
public void resolveResourceFromWindowsFilesystem() throws Exception {
3647
// This path is invalid - it should return null even on Windows.
@@ -76,4 +87,17 @@ public void resolveRootResourcesFromArchive() throws Exception {
7687
assertTrue(loader.getResources("").hasMoreElements());
7788
}
7889

90+
@Test
91+
public void resolveFromNested() throws Exception {
92+
File file = this.temporaryFolder.newFile();
93+
TestJarCreator.createTestJar(file);
94+
JarFile jarFile = new JarFile(file);
95+
URL url = jarFile.getUrl();
96+
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url },
97+
null);
98+
URL resource = loader.getResource("nested.jar!/3.dat");
99+
assertThat(resource.toString(), equalTo(url + "nested.jar!/3.dat"));
100+
assertThat(resource.openConnection().getInputStream().read(), equalTo(3));
101+
}
102+
79103
}

spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,16 @@ public void jarFileWithScriptAtTheStart() throws Exception {
408408
getEntries();
409409
getNestedJarFile();
410410
}
411+
412+
@Test
413+
public void cannotLoadMissingJar() throws Exception {
414+
// relates to gh-1070
415+
JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
416+
.getEntry("nested.jar"));
417+
URL nestedUrl = nestedJarFile.getUrl();
418+
URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat");
419+
this.thrown.expect(FileNotFoundException.class);
420+
url.openConnection().getInputStream();
421+
}
422+
411423
}

0 commit comments

Comments
 (0)