Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Make class loader more roboust in the face of weird path/file name ch…
…aracters. Prefer URI over URLEncoder/URLDecoder.
  • Loading branch information
Arne Georg Gleditsch committed Jun 3, 2014
commit 52c1b9b313ac92798b4bc17b78e2397462c1dabd
4 changes: 2 additions & 2 deletions emjar-maven-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Maven Plugin Mojo for building bundling jars that contain dependency artifact ja
<plugin>
<groupId>com.comoyo.commons</groupId>
<artifactId>emjar-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.0.33</version>
<executions>
<execution>
<goals>
Expand All @@ -67,7 +67,7 @@ Maven Plugin Mojo for building bundling jars that contain dependency artifact ja
<plugin>
<groupId>com.comoyo.commons</groupId>
<artifactId>emjar-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.0.33</version>
<executions>
<execution>
<goals>
Expand Down
6 changes: 6 additions & 0 deletions emjar/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,11 @@
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google.guava.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
80 changes: 57 additions & 23 deletions emjar/src/main/java/com/comoyo/emjar/EmJarClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.comoyo.emjar;

import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
Expand All @@ -29,6 +31,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.File;
import java.io.IOException;

Expand Down Expand Up @@ -67,6 +71,9 @@
public class EmJarClassLoader
extends URLClassLoader
{
public final static String SEPARATOR = "!/";

private final static Logger logger = Logger.getLogger(EmJarClassLoader.class.getName());
private final static HandlerFactory factory = new HandlerFactory();

static {
Expand All @@ -79,17 +86,21 @@ public class EmJarClassLoader

public EmJarClassLoader()
{
super(getClassPath(), null, factory);
super(getClassPath(System.getProperties()), null, factory);
}

public EmJarClassLoader(ClassLoader parent)
{
super(getClassPath(), parent, factory);
super(getClassPath(System.getProperties()), parent, factory);
}

private static URL[] getClassPath()
protected EmJarClassLoader(Properties props)
{
super(getClassPath(props), null, factory);
}

private static URL[] getClassPath(final Properties props)
{
final Properties props = System.getProperties();
final String classPath = props.getProperty("java.class.path");
final String pathSep = props.getProperty("path.separator");
final String fileSep = props.getProperty("file.separator");
Expand All @@ -102,19 +113,20 @@ private static URL[] getClassPath()
}
final String full = elem.startsWith(fileSep) ? elem : userDir + fileSep + elem;
try {
urls.add(new URL("file:" + full));
urls.add(new URI("file", full, null).toURL());
final JarFile jar = new JarFile(elem);
Enumeration<JarEntry> embedded = jar.entries();
final Enumeration<JarEntry> embedded = jar.entries();
while (embedded.hasMoreElements()) {
final JarEntry entry = embedded.nextElement();
if (entry.getName().endsWith(".jar")) {
urls.add(new URL("jar:file:" + full + "!/" + entry.getName()));
urls.add(new URI("jar:file", full + SEPARATOR + entry.getName(), null).toURL());
}
}
jar.close();
}
catch (IOException e) {
e.printStackTrace();
catch (IOException|URISyntaxException e) {
logger.log(Level.SEVERE, "Unable to process classpath entry " + elem, e);
// Trying to get by on the classpath entries we can process.
}
}
return urls.toArray(new URL[0]);
Expand Down Expand Up @@ -151,23 +163,45 @@ private static class Handler
protected URLConnection openConnection(URL url)
throws IOException
{
final String spec = url.getFile();
if (!spec.startsWith("jar:file:")) {
throw new IOException("Unable to handle " + spec);
final URI bundle;
final URI file;
try {
final URI nested = url.toURI();
if (!"jar".equals(nested.getScheme())) {
throw new IOException(
"Unexpected nested scheme passed to openConnection (expeced jar): "
+ nested.getScheme());
}
bundle = new URI(nested.getRawSchemeSpecificPart());
if (!"jar".equals(bundle.getScheme())) {
throw new IOException(
"Unexpected bundle scheme passed to openConnection (expected jar): "
+ bundle.getScheme());
}
file = new URI(bundle.getRawSchemeSpecificPart());
if (!"file".equals(file.getScheme())) {
throw new IOException(
"Unexpected location scheme passed to openConnection (expected file): "
+ file.getScheme());
}
}
catch (URISyntaxException e) {
throw new IOException(e);
}
JarURLConnection conn = connections.get(spec);
final String path = file.getSchemeSpecificPart();
JarURLConnection conn = connections.get(path);
if (conn == null) {
synchronized (connections) {
conn = connections.get(spec);
conn = connections.get(path);
if (conn == null) {
final int i = spec.indexOf("!/");
final int j = spec.indexOf("!/", i + 1);
final int i = path.indexOf(SEPARATOR);
final int j = path.indexOf(SEPARATOR, i + 1);
if (i < 0 || j < 0) {
throw new IOException("Unable to parse " + spec);
throw new IOException("Unable to parse " + path);
}
final String root = spec.substring(9, i);
final String nested = spec.substring(i + 2, j);
final String entry = spec.substring(j + 2);
final String root = path.substring(0, i);
final String nested = path.substring(i + SEPARATOR.length(), j);
final String entry = path.substring(j + SEPARATOR.length());

Map<String, Map<String, OndemandEmbeddedJar.Descriptor>> rootJar
= rootJars.get(root);
Expand All @@ -186,13 +220,13 @@ protected URLConnection openConnection(URL url)
= rootJar.get(nested);
if (descriptors != null) {
conn = new OndemandEmbeddedJar.Connection(
new URL(spec), root, descriptors, entry);
bundle.toURL(), root, descriptors, entry);
}
else {
conn = new PreloadedEmbeddedJar.Connection(
new URL(spec), root, nested, entry);
bundle.toURL(), root, nested, entry);
}
connections.put(spec, conn);
connections.put(path, conn);
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions emjar/src/test/java/com/comoyo/emjar/EmJarClassLoaderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (C) 2014 Telenor Digital AS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.comoyo.emjar;

import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.google.common.base.Joiner;

import static org.junit.Assert.*;

@RunWith(JUnit4.class)
public class EmJarClassLoaderTest extends EmJarTest
{
@Test
public void testClassPathQuoting()
throws Exception
{
final FilenameFilter jarFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
};
final File bundle = getResourceFile("bundle-s-large.jar");
final File allJars[] = bundle.getParentFile().listFiles(jarFilter);

final Properties props = new Properties();
props.setProperty("java.class.path", Joiner.on(":").join(allJars));
props.setProperty("path.separator", ":");
props.setProperty("file.separator", "/");
props.setProperty("user.dir", "/tmp");
final EmJarClassLoader loader = new EmJarClassLoader(props);
final InputStream is = loader.getResourceAsStream("entry-" + WEIRD + ".txt");
final BufferedReader entry = new BufferedReader(new InputStreamReader(is));
assertEquals("Contents mismatch for weird entry", WEIRD, entry.readLine());
}
}
18 changes: 11 additions & 7 deletions emjar/src/test/java/com/comoyo/emjar/EmJarTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
package com.comoyo.emjar;

import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URI;
import java.net.URISyntaxException;

public abstract class EmJarTest
{
protected File getResourceFile(String name) {
public static final String WEIRD = "æøå😱 %&;*+`\"\\-weird";

protected File getResourceFile(String name)
throws URISyntaxException
{
final ClassLoader cl = getClass().getClassLoader();
final URL url = cl.getResource("com/comoyo/emjar/" + name);
if (!"file".equals(url.getProtocol())) {
final URI uri = cl.getResource("com/comoyo/emjar/" + name).toURI();
if (!"file".equals(uri.getScheme())) {
throw new IllegalArgumentException(
"Resource " + name + " not present as file (" + url + ")");
"Resource " + name + " not present as file (" + uri + ")");
}
return new File(URLDecoder.decode(url.getPath()));
return new File(uri.getSchemeSpecificPart());
}
}
7 changes: 7 additions & 0 deletions emjar/src/test/java/com/comoyo/emjar/JarTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,11 @@ public void testAllBundles()
}
}
}

@Test
public void testQuoting()
throws Exception
{
testJarBundle(WEIRD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.io.File;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URI;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
Expand All @@ -42,7 +42,7 @@ public JarURLConnection getJarUrlConnection(File root, String jarName, String en
= scanner.scan();
assertNotNull("Descriptor returned from ZipScanner was null", desc);
return new OndemandEmbeddedJar.Connection(
new URL("jar:file:" + root.getPath() + "!/" + jarName + "!/"),
new URI("jar:file", root.getPath() + "!/" + jarName + "!/", null).toURL(),
root.getPath(),
desc.get(jarName),
entryName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.io.File;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URI;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

Expand All @@ -37,7 +37,7 @@ public JarURLConnection getJarUrlConnection(File root, String jarName, String en
throws Exception
{
return new PreloadedEmbeddedJar.Connection(
new URL("jar:file:" + root.getPath() + "!/" + jarName + "!/"),
new URI("jar:file", root.getPath() + "!/" + jarName + "!/", null).toURL(),
root.getPath(),
jarName,
entryName);
Expand Down
Binary file not shown.
1 change: 1 addition & 0 deletions emjar/src/test/resources/com/comoyo/emjar/create-zips.pl
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ sub createZip {
createZip("s-large", {Stream => 0}, {}, 1);
createZip("S-large", {Stream => 1}, {}, 1);
createZip("Z-large", {}, {}, 1, ZIP_CM_DEFLATE);
createZip("æøå😱 %&;*+`\"\\-weird", {}, {});