Commit 53cb8ccd authored by Martin Lau's avatar Martin Lau Committed by Phillip Webb

Escape URL characters in JAR URLs

Update the spring-boot-loader JarURLConnection class to decode entry
names in the same way as the stock JDK class. This allows encoded
entry names in the form `%c3%ab` to be loaded.

Fixes gh-556
parent f852096c
...@@ -16,9 +16,11 @@ ...@@ -16,9 +16,11 @@
package org.springframework.boot.loader.jar; package org.springframework.boot.loader.jar;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.jar.Manifest; import java.util.jar.Manifest;
...@@ -64,7 +66,7 @@ class JarURLConnection extends java.net.JarURLConnection { ...@@ -64,7 +66,7 @@ class JarURLConnection extends java.net.JarURLConnection {
*/ */
if (separator + SEPARATOR.length() != spec.length()) { if (separator + SEPARATOR.length() != spec.length()) {
this.jarFileUrl = new URL("jar:" + spec); this.jarFileUrl = new URL("jar:" + spec);
this.jarEntryName = spec.substring(separator + 2); this.jarEntryName = decode(spec.substring(separator + 2));
} }
else { else {
// The root of the archive (!/) // The root of the archive (!/)
...@@ -162,4 +164,39 @@ class JarURLConnection extends java.net.JarURLConnection { ...@@ -162,4 +164,39 @@ class JarURLConnection extends java.net.JarURLConnection {
return builder.toString(); return builder.toString();
} }
private static String decode(String source) {
int length = source.length();
if ((length == 0) || (source.indexOf('%') < 0)) {
return source;
}
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
for (int i = 0; i < length; i++) {
int ch = source.charAt(i);
if (ch == '%') {
if ((i + 2) >= length) {
throw new IllegalArgumentException("Invalid encoded sequence \""
+ source.substring(i) + "\"");
}
ch = decodeEscapeSequence(source, i);
i += 2;
}
bos.write(ch);
}
return new String(bos.toByteArray(), "UTF-8");
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
private static char decodeEscapeSequence(String source, int i) {
int hi = Character.digit(source.charAt(i + 1), 16);
int lo = Character.digit(source.charAt(i + 2), 16);
if (hi == -1 || lo == -1) {
throw new IllegalArgumentException("Invalid encoded sequence \""
+ source.substring(i) + "\"");
}
return ((char) ((hi << 4) + lo));
}
} }
...@@ -43,6 +43,8 @@ public abstract class TestJarCreator { ...@@ -43,6 +43,8 @@ public abstract class TestJarCreator {
writeEntry(jarOutputStream, "2.dat", 2); writeEntry(jarOutputStream, "2.dat", 2);
writeDirEntry(jarOutputStream, "d/"); writeDirEntry(jarOutputStream, "d/");
writeEntry(jarOutputStream, "d/9.dat", 9); writeEntry(jarOutputStream, "d/9.dat", 9);
writeDirEntry(jarOutputStream, "special/");
writeEntry(jarOutputStream, "special/\u00EB.dat", '\u00EB');
JarEntry nestedEntry = new JarEntry("nested.jar"); JarEntry nestedEntry = new JarEntry("nested.jar");
byte[] nestedJarData = getNestedJarData(); byte[] nestedJarData = getNestedJarData();
...@@ -68,6 +70,7 @@ public abstract class TestJarCreator { ...@@ -68,6 +70,7 @@ public abstract class TestJarCreator {
writeManifest(jarOutputStream, "j2"); writeManifest(jarOutputStream, "j2");
writeEntry(jarOutputStream, "3.dat", 3); writeEntry(jarOutputStream, "3.dat", 3);
writeEntry(jarOutputStream, "4.dat", 4); writeEntry(jarOutputStream, "4.dat", 4);
writeEntry(jarOutputStream, "\u00E4.dat", '\u00E4');
jarOutputStream.close(); jarOutputStream.close();
return byteArrayOutputStream.toByteArray(); return byteArrayOutputStream.toByteArray();
} }
......
...@@ -100,7 +100,7 @@ public class ExplodedArchiveTests { ...@@ -100,7 +100,7 @@ public class ExplodedArchiveTests {
@Test @Test
public void getEntries() throws Exception { public void getEntries() throws Exception {
Map<String, Archive.Entry> entries = getEntriesMap(this.archive); Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
assertThat(entries.size(), equalTo(7)); assertThat(entries.size(), equalTo(9));
} }
@Test @Test
......
...@@ -62,7 +62,7 @@ public class JarFileArchiveTests { ...@@ -62,7 +62,7 @@ public class JarFileArchiveTests {
@Test @Test
public void getEntries() throws Exception { public void getEntries() throws Exception {
Map<String, Archive.Entry> entries = getEntriesMap(this.archive); Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
assertThat(entries.size(), equalTo(7)); assertThat(entries.size(), equalTo(9));
} }
@Test @Test
......
...@@ -21,6 +21,7 @@ import java.io.FileNotFoundException; ...@@ -21,6 +21,7 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.Manifest; import java.util.jar.Manifest;
...@@ -50,6 +51,7 @@ import static org.mockito.Mockito.verify; ...@@ -50,6 +51,7 @@ import static org.mockito.Mockito.verify;
* Tests for {@link JarFile}. * Tests for {@link JarFile}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Martin Lau
*/ */
public class JarFileTests { public class JarFileTests {
...@@ -70,6 +72,26 @@ public class JarFileTests { ...@@ -70,6 +72,26 @@ public class JarFileTests {
this.jarFile = new JarFile(this.rootJarFile); this.jarFile = new JarFile(this.rootJarFile);
} }
@Test
public void jdkJarFile() throws Exception {
// Sanity checks to see how the default jar file operates
java.util.jar.JarFile jarFile = new java.util.jar.JarFile(this.rootJarFile);
Enumeration<java.util.jar.JarEntry> entries = jarFile.entries();
assertThat(entries.nextElement().getName(), equalTo("META-INF/"));
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
assertThat(entries.nextElement().getName(), equalTo("1.dat"));
assertThat(entries.nextElement().getName(), equalTo("2.dat"));
assertThat(entries.nextElement().getName(), equalTo("d/"));
assertThat(entries.nextElement().getName(), equalTo("d/9.dat"));
assertThat(entries.nextElement().getName(), equalTo("special/"));
assertThat(entries.nextElement().getName(), equalTo("special/\u00EB.dat"));
assertThat(entries.nextElement().getName(), equalTo("nested.jar"));
assertThat(entries.hasMoreElements(), equalTo(false));
URL jarUrl = new URL("jar:" + this.rootJarFile.toURI() + "!/");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { jarUrl });
assertThat(urlClassLoader.getResource("special/\u00EB.dat"), notNullValue());
}
@Test @Test
public void createFromFile() throws Exception { public void createFromFile() throws Exception {
JarFile jarFile = new JarFile(this.rootJarFile); JarFile jarFile = new JarFile(this.rootJarFile);
...@@ -99,10 +121,19 @@ public class JarFileTests { ...@@ -99,10 +121,19 @@ public class JarFileTests {
assertThat(entries.nextElement().getName(), equalTo("2.dat")); assertThat(entries.nextElement().getName(), equalTo("2.dat"));
assertThat(entries.nextElement().getName(), equalTo("d/")); assertThat(entries.nextElement().getName(), equalTo("d/"));
assertThat(entries.nextElement().getName(), equalTo("d/9.dat")); assertThat(entries.nextElement().getName(), equalTo("d/9.dat"));
assertThat(entries.nextElement().getName(), equalTo("special/"));
assertThat(entries.nextElement().getName(), equalTo("special/\u00EB.dat"));
assertThat(entries.nextElement().getName(), equalTo("nested.jar")); assertThat(entries.nextElement().getName(), equalTo("nested.jar"));
assertThat(entries.hasMoreElements(), equalTo(false)); assertThat(entries.hasMoreElements(), equalTo(false));
} }
@Test
public void getSpecialResourceViaClassLoader() throws Exception {
URLClassLoader urlClassLoader = new URLClassLoader(
new URL[] { this.jarFile.getUrl() });
assertThat(urlClassLoader.getResource("special/\u00EB.dat"), notNullValue());
}
@Test @Test
public void getJarEntry() throws Exception { public void getJarEntry() throws Exception {
java.util.jar.JarEntry entry = this.jarFile.getJarEntry("1.dat"); java.util.jar.JarEntry entry = this.jarFile.getJarEntry("1.dat");
...@@ -204,6 +235,7 @@ public class JarFileTests { ...@@ -204,6 +235,7 @@ public class JarFileTests {
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF")); assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
assertThat(entries.nextElement().getName(), equalTo("3.dat")); assertThat(entries.nextElement().getName(), equalTo("3.dat"));
assertThat(entries.nextElement().getName(), equalTo("4.dat")); assertThat(entries.nextElement().getName(), equalTo("4.dat"));
assertThat(entries.nextElement().getName(), equalTo("\u00E4.dat"));
assertThat(entries.hasMoreElements(), equalTo(false)); assertThat(entries.hasMoreElements(), equalTo(false));
InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile
......
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