├── .github
└── workflows
│ └── maven.yml
├── .gitignore
├── README.md
├── pom.xml
└── src
└── main
└── java
└── com
└── github
└── msx80
└── wiseloader
├── ArbitratorClassLoader.java
├── BytesLoader.java
├── ClassNotAllowedException.java
├── MainWiseloader.java
├── WhitelistClassLoader.java
├── WhitelistedJDKClasses.java
└── loaders
├── FileUtil.java
├── JarLoader.java
├── MultiBytesLoader.java
└── compiler
├── ClassFolder.java
├── CompilationError.java
├── CompilingLoader.java
├── CustomJavaFileManager.java
├── Inferable.java
├── JarFileObject.java
├── JarFolder.java
└── JavaSourceFromString.java
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 8
20 | uses: actions/setup-java@v2
21 | with:
22 | java-version: '8'
23 | distribution: 'adopt'
24 | - name: Build with Maven
25 | run: mvn -B package --file pom.xml
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | target/
14 |
15 | .settings/
16 | .classpath
17 | .project
18 |
19 | # Package Files #
20 | *.jar
21 | *.war
22 | *.nar
23 | *.ear
24 | *.zip
25 | *.tar.gz
26 | *.rar
27 |
28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
29 | hs_err_pid*
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  [](https://jitpack.io/#msx80/wiseloader)
2 |
3 | # WiseLoader
4 |
5 | WiseLoader (Whitelist Secured Environment Loader or something) is a Java Classloader that loads class from a given set of whitelist class names. Attempting to load classes not in the whitelist will result in an error
6 |
7 | ## Why
8 |
9 | Following [JEP 411: Deprecate the Security Manager for Removal](https://openjdk.java.net/jeps/411), it will be impossible to load third party classes in a sandboxed environment. All classes will have the same permission within a VM (typically the permissions of the user running the vm). This will make it exceptionally dangerous to run external, untrusted jars. A typical use case for this is an application plugin system. For example, you build an application (ie, a graphic editor like Gimp) that allow users to develop their own plugins to add new functionalities. Plugins could be distributed as Jars on some "store like" site or something. Now, without a security manager, it will be impossible to grant safety for the final user, without careful inspection of the sources of all plugins. Jars could have malicious code that deletes unrelated user files or worst.
10 |
11 | This project explores a different approach. It loads external classes with a classloader that is only allowed to access a predetermined set of classes. You can provide your own list of classes, and/or use a (partial, work in progress) [list of all "safe" JDK classes](https://github.com/msx80/WiseLoader/blob/main/src/main/java/com/github/msx80/wiseloader/WhitelistedJDKClasses.java) that is provided, where "safe" means it only works on data in memory and doesn't deal with files, sockets, classloading, reflection, native interfaces or other unsecure activities.
12 |
13 | Without access to classes that communicates in any way with the machine, it should be impossible for an untrusted jar to cause damage.
14 |
15 | NOTE: as of now this project is experimental, i'm not sure it can provide complete safety. You're more than welcome to experiment and try and break it!
16 |
17 | # How to use
18 |
19 | Something like this will be the typical usage:
20 |
21 | BytesLoader bl = new JarLoader(new File(myJarFile));
22 | WhitelistClassLoader d = new WhitelistClassLoader(bl, WhitelistedJDKClasses.LIST);
23 | Class> c = d.loadClass("untrustedclass.PluginExample");
24 | MyInterface o = (MyInterface)c.newInstance();
25 |
26 | # Limitations
27 |
28 | Some generally useful jre classes are not safe, best example being java.lang.System. System has unsafe methods like exit(), loadLibrary(), get/setProperty, etc. Since it also has commonly used stuff like in/out, currentTimeMillis() etc, it can be a problem for plugin writers that can't access them anymore. In this case, the host application can provide their own objects to act as bridges in their API, and expose only the interesting stuff, something like this:
29 |
30 | public class SecureSystem {
31 | public static long currentTimeMillis()
32 | {
33 | return System.currentTimeMillis();
34 | }
35 | ...
36 | }
37 |
38 | (The SecureSystem will need to be whitelisted, obviously).
39 |
40 | # How it works
41 |
42 | When the classloader is asked to load a class, it checks if it's whitelisted. If it is, it first try to load it from the parent (the standard) classloader, if it doesn't find it it loads from the "secured context", ie from the jar passed to the WhitelistClassLoader. If the class is not whitelisted, an attempt is made to load it from the secured context (with some exception: you can't load classes from the java* package), failing that an exception is thrown.
43 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.github.msx80
4 | wiseloader
5 | 1.0.5
6 |
7 |
8 |
9 | net.lingala.zip4j
10 | zip4j
11 | 2.9.1
12 |
13 |
14 |
15 |
16 |
17 | org.apache.maven.plugins
18 | maven-compiler-plugin
19 | 3.3
20 |
21 | 1.8
22 | 1.8
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/ArbitratorClassLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.InputStream;
5 | import java.net.URL;
6 |
7 | public abstract class ArbitratorClassLoader extends ClassLoader
8 | {
9 |
10 | private ClassLoader parent = ArbitratorClassLoader.class.getClassLoader();
11 | private BytesLoader loader;
12 | private boolean prioritizeParent;
13 |
14 | public ArbitratorClassLoader(BytesLoader loader, boolean prioritizeParent)
15 | {
16 | this.loader = loader;
17 | this.prioritizeParent = prioritizeParent;
18 |
19 | }
20 |
21 | public ArbitratorClassLoader(BytesLoader loader, ClassLoader parent, boolean prioritizeParent)
22 | {
23 | this.loader = loader;
24 | this.parent = parent;
25 | this.prioritizeParent = prioritizeParent;
26 | }
27 |
28 | @Override
29 | public Class> loadClass(String name) throws ClassNotFoundException
30 | {
31 | System.out.println("Accessing class "+name);
32 |
33 | if(allowClass(name))
34 | {
35 | return loadAllowedClass(name);
36 | }
37 | else
38 | {
39 | if(name.startsWith("java"))
40 | {
41 | throw new ClassNotAllowedException(name, "Class "+name+" is not whitelisted.");
42 | }
43 | Class> a = loadClassFromSecuredContext(name);
44 | if(a==null) throw new ClassNotAllowedException(name, "Class "+name+" is not whitelisted and was not found secured context.");
45 | return a;
46 | }
47 | }
48 |
49 | protected abstract boolean allowClass(String name);
50 |
51 | private Class> loadClassFromSecuredContext(String name) throws ClassNotFoundException {
52 |
53 | // Just try in custom classloader ?
54 |
55 | byte[] newClassData;
56 | try {
57 | newClassData = loadNewClass(name);
58 | } catch (Exception e1) {
59 | throw new ClassNotFoundException("Error loading class", e1);
60 | }
61 | if (newClassData != null) {
62 | System.out.println("Loaded non whitelisted class from secured context: "+name);
63 | return define(newClassData, name);
64 | } else {
65 | return null;
66 | }
67 | }
68 |
69 | private Class> loadAllowedClass(String name) throws ClassNotFoundException {
70 | // allowed class, try the parent classloader first and eventually try the custom loader
71 | if(prioritizeParent)
72 | {
73 | try {
74 | return parent.loadClass(name);
75 | } catch (ClassNotFoundException e) {
76 | Class> a = loadClassFromSecuredContext(name);
77 | if(a==null) throw new ClassNotFoundException("Class "+name+" not found.");
78 | return a;
79 | }
80 | }
81 | else
82 | {
83 | try {
84 | Class> a = loadClassFromSecuredContext(name);
85 | if(a==null) throw new ClassNotFoundException("Class "+name+" not found.");
86 | return a;
87 | } catch (ClassNotFoundException e) {
88 | return parent.loadClass(name);
89 | }
90 | }
91 | }
92 |
93 |
94 | private byte[] loadNewClass(String name) throws Exception {
95 | System.out.println("Loading bytes for class " + name);
96 | String filePath = toFilePath(name);
97 | return loader.loadFile(filePath);
98 |
99 | }
100 | private Class> define(byte[] classData, String name) {
101 | Class> clazz = defineClass(name, classData, 0, classData.length);
102 | if (clazz != null) {
103 | if (clazz.getPackage() == null) {
104 | definePackage(name.replaceAll("\\.\\w+$", ""), null, null, null, null, null, null, null);
105 | }
106 | resolveClass(clazz);
107 | }
108 | return clazz;
109 | }
110 |
111 | @Override
112 | public InputStream getResourceAsStream(String name) {
113 |
114 | try {
115 | byte[] data = loader.loadFile(name);
116 | if (data== null)
117 | return null;
118 | else
119 | return new ByteArrayInputStream(data);
120 |
121 | } catch (Exception e) {
122 | throw new RuntimeException("Unable to read resource", e);
123 | }
124 | }
125 |
126 |
127 | public static String toFilePath(String name) {
128 | return name.replaceAll("\\.", "/") + ".class";
129 | }
130 |
131 | @Override
132 | public URL getResource(String name) {
133 | throw new UnsupportedOperationException("getResource can't return a meaningful URL, use getResourceAsStream");
134 | }
135 |
136 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/BytesLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader;
2 |
3 |
4 | /**
5 | * Load bytes from some source, by name.
6 | *
7 | */
8 | public interface BytesLoader
9 | {
10 | public byte[] loadFile(String name) throws Exception;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/ClassNotAllowedException.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader;
2 |
3 | public class ClassNotAllowedException extends ClassNotFoundException {
4 |
5 | private static final long serialVersionUID = -8134219137661015582L;
6 |
7 | public final String className;
8 |
9 | public ClassNotAllowedException(String className) {
10 | this.className = className;
11 | }
12 |
13 | public ClassNotAllowedException(String className, String s) {
14 | super(s);
15 | this.className = className;
16 | }
17 |
18 | public ClassNotAllowedException(String className, String s, Throwable ex) {
19 | super(s, ex);
20 | this.className = className;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/MainWiseloader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader;
2 |
3 | import java.io.File;
4 | import java.io.InputStream;
5 | import java.nio.file.Files;
6 | import java.nio.file.Paths;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.stream.Collectors;
10 |
11 | import com.github.msx80.wiseloader.loaders.JarLoader;
12 | import com.github.msx80.wiseloader.loaders.compiler.CompilingLoader;
13 |
14 | public class MainWiseloader {
15 |
16 | public static void main(String[] args) throws Exception
17 | {
18 | testCompilation();
19 | BytesLoader loader = new JarLoader(new File("C:\\Users\\niclugat\\dev\\omicron\\demo\\Snake\\SnakeMain.omicron"));
20 |
21 | WhitelistClassLoader d = new WhitelistClassLoader(loader, true, WhitelistedJDKClasses.LIST);
22 | //d.allowClasses("com.github.msx80.omicron.api.Game");
23 | InputStream is = d.getResourceAsStream("/omicron/demo/snake/sheet2.png");
24 | System.out.println("Resource: "+is);
25 | Class> c = d.loadClass("omicron.demo.snake.SnakeMain");
26 | System.out.println(c.newInstance());
27 | }
28 |
29 | private static void testCompilation() throws Exception
30 | {
31 |
32 | String demo = Files.readAllLines(Paths.get("Demo.java")).stream().collect(Collectors.joining("\n"));
33 | System.out.println(demo);
34 | Map classes = new HashMap<>();
35 | classes.put("wise.demo.Demo", demo);
36 | WhitelistClassLoader d = new WhitelistClassLoader(new CompilingLoader(classes, new HashMap<>()), true);
37 | d.allowClasses(WhitelistedJDKClasses.LIST);
38 | d.allowClasses(
39 | "java.lang.Class",
40 | "java.nio.file.Paths"
41 | );
42 | Class> c = d.loadClass("wise.demo.Demo");
43 | System.out.println(c.newInstance());
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/WhitelistClassLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collection;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 |
9 | public class WhitelistClassLoader extends ArbitratorClassLoader
10 | {
11 | private final Set allowedClasses = new HashSet<>();
12 |
13 | public WhitelistClassLoader(BytesLoader loader, boolean prioritizeParent, String... allowedClasses)
14 | {
15 | super(loader, prioritizeParent);
16 | this.allowedClasses.addAll(Arrays.asList(allowedClasses));
17 | }
18 |
19 | public WhitelistClassLoader(BytesLoader loader, ClassLoader parent, boolean prioritizeParent, String... allowedClasses)
20 | {
21 | super(loader, parent, prioritizeParent);
22 | this.allowedClasses.addAll(Arrays.asList(allowedClasses));
23 | }
24 |
25 | public WhitelistClassLoader allowClasses(Collection classes)
26 | {
27 | this.allowedClasses.addAll(classes);
28 | return this;
29 | }
30 |
31 | public WhitelistClassLoader allowClasses(String... classes)
32 | {
33 | this.allowedClasses.addAll(Arrays.asList(classes));
34 | return this;
35 | }
36 |
37 | @Override
38 | protected boolean allowClass(String name)
39 | {
40 | return allowedClasses.contains(name);
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/WhitelistedJDKClasses.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader;
2 |
3 | public class WhitelistedJDKClasses {
4 |
5 | // TODO to be expanded
6 |
7 | public static final String[] LIST =
8 | {
9 |
10 | "java.math.BigInteger",
11 | "java.math.BigDecimal",
12 | "java.io.ByteArrayInputStream",
13 | "java.io.ByteArrayOutputStream",
14 | "java.io.InputStream", // abstract, access nothing
15 | "java.io.OutputStream", // abstract, access nothing
16 | "java.util.zip.GZIPInputStream", // based on InputStream, no access to resources
17 | "java.util.zip.GZIPOutputStream", // based on OutputStream, no access to resources
18 | "java.io.IOException",
19 | "java.io.Serializable",
20 | "java.io.EOFException",
21 | "java.util.Map$Entry",
22 | "java.util.Arrays",
23 | "java.lang.CharSequence",
24 | "java.lang.Character",
25 | "java.lang.Comparable",
26 | "java.lang.Double",
27 | "java.lang.Enum",
28 | "java.lang.Boolean",
29 | "java.util.concurrent.Callable",
30 | "java.lang.Exception",
31 | "java.lang.Float",
32 | "java.lang.Integer",
33 | "java.lang.Iterable",
34 | "java.lang.Long",
35 | "java.lang.Math",
36 | "java.lang.NoSuchFieldError",
37 | "java.lang.Object",
38 | "java.lang.Runnable",
39 | "java.lang.RuntimeException",
40 | "java.lang.IllegalArgumentException",
41 | "java.lang.String",
42 | "java.lang.StringBuilder",
43 | "java.lang.StringBuffer",
44 | "java.lang.Throwable",
45 | "java.lang.invoke.LambdaMetafactory",
46 | "java.lang.invoke.StringConcatFactory", // TODO check this one
47 | "java.nio.charset.Charset",
48 | "java.util.ArrayList",
49 | "java.util.Collection",
50 | "java.util.HashMap",
51 | "java.util.HashSet",
52 | "java.util.Iterator",
53 | "java.util.LinkedList",
54 | "java.util.List",
55 | "java.util.Map",
56 | "java.util.Random",
57 | "java.util.Comparator",
58 | "java.util.Set",
59 | "java.util.Stack",
60 | "java.util.StringJoiner",
61 | "java.util.Vector",
62 | "java.util.function.Consumer",
63 | "java.util.function.Function",
64 | "java.util.function.BiFunction",
65 | "java.util.function.IntConsumer",
66 | "java.util.function.IntPredicate",
67 | "java.util.function.IntSupplier",
68 | "java.util.function.Predicate",
69 | "java.util.function.ToIntFunction",
70 | "java.util.function.ToDoubleFunction",
71 | "java.util.stream.IntStream",
72 | "java.util.function.Supplier",
73 | "java.util.regex.Pattern",
74 | "java.util.stream.Collectors",
75 | "java.util.stream.Stream", // imports java.nio.file.Files and java.nio.file.Path but doesn't seems to use it
76 | "java.io.Externalizable",
77 | "java.lang.Cloneable",
78 | "java.lang.UnsupportedOperationException",
79 | "java.text.DecimalFormat",
80 | "java.text.NumberFormat"
81 | };
82 |
83 |
84 | // Unused, but keep tracks of classes that are known to be unsafe
85 | // so they don't get researched multiple times.
86 | @SuppressWarnings("unused")
87 | private static final String[] BAD_CLASSES =
88 | {
89 | "java.lang.System", // has exit(), loadLibrary(), etc..
90 | "java.lang.Runtime", // can execute processes, exit() etc..
91 | "java.lang.Thread", // not exacly bad per se but probably is not ok to start threads
92 | "java.lang.Process", // can start external programs
93 | };
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.Closeable;
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.Flushable;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.OutputStream;
12 | import java.util.Properties;
13 |
14 | public class FileUtil {
15 |
16 |
17 | public static Properties loadProps(byte[] prop) throws IOException {
18 | Properties p = new Properties();
19 | p.load(new ByteArrayInputStream(prop));
20 | return p;
21 | }
22 |
23 | public static void copy(InputStream in, OutputStream out)
24 | throws IOException
25 | {
26 | // Read bytes and write to destination until eof
27 |
28 | byte[] buf = new byte[1024*4];
29 | int len = 0;
30 | while ((len = in.read(buf)) >= 0)
31 | {
32 | out.write(buf, 0, len);
33 | }
34 | }
35 |
36 |
37 | /**
38 | *
39 | * @param fileToRead
40 | * @return
41 | * @throws IOException
42 | */
43 | public static byte[] readFileToBytes(File fileToRead) {
44 | try {
45 | return readData(new FileInputStream(fileToRead));
46 | } catch (IOException e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | public static byte[] readData(InputStream inputStream) {
52 | try {
53 | return readDataNice(inputStream);
54 | } finally {
55 | close(inputStream);
56 | }
57 | }
58 |
59 | public static byte[] readDataNice(InputStream inputStream) {
60 | ByteArrayOutputStream boTemp = null;
61 | byte[] buffer = null;
62 | try {
63 | int read;
64 | buffer = new byte[8192];
65 | boTemp = new ByteArrayOutputStream();
66 | while ((read=inputStream.read(buffer, 0, 8192)) > -1) {
67 | boTemp.write(buffer, 0, read);
68 | }
69 | return boTemp.toByteArray();
70 | } catch (IOException e) {
71 | throw new RuntimeException(e);
72 | }
73 | }
74 |
75 |
76 | /**
77 | * Close streams (in or out)
78 | * @param stream
79 | */
80 | public static void close(Closeable stream) {
81 | if (stream != null) {
82 | try {
83 | if (stream instanceof Flushable) {
84 | ((Flushable)stream).flush();
85 | }
86 | stream.close();
87 | } catch (IOException e) {
88 | // When the stream is closed or interupted, can ignore this exception
89 | }
90 | }
91 |
92 |
93 |
94 |
95 |
96 | }
97 |
98 |
99 |
100 | /*
101 | public static FileInputStream fileInputStream(File file) {
102 | try {
103 | return new FileInputStream(file);
104 | } catch (FileNotFoundException e) {
105 | throw new RuntimeException(e);
106 | }
107 | }
108 | public static FileInputStream fileInputStream(String file) {
109 | try {
110 | return new FileInputStream(file);
111 | } catch (FileNotFoundException e) {
112 | throw new RuntimeException(e);
113 | }
114 | }
115 |
116 |
117 | public static void eachFile(File path, P2 f) {
118 | eachFile(path, f, null);
119 | }
120 |
121 | public static void eachFile(File path, P2 f, F1 exclude) {
122 | eachFile(path, Fs.f2(f, true), exclude);
123 | }
124 |
125 | public static void eachFile(File path, F2 f, F1 exclude) {
126 |
127 | ArrayList relPath = new ArrayList<>();
128 |
129 | if (path.isFile()) {
130 | f.e(path, Cols.join(relPath, File.separator));
131 | } else {
132 | if (!eachFileInDir(path, f, relPath, exclude)) return;
133 | }
134 | }
135 |
136 | private static boolean eachFileInDir(File path, F2 f, ArrayList relPath, F1 exclude) {
137 | if (!path.exists() || !path.isDirectory()) {
138 | throw new RuntimeException("Invalid path: " + path);
139 | }
140 | for (File child : path.listFiles()) {
141 | if (exclude != null && exclude.e(child)) {
142 | // System.out.println("Excluded " + child);
143 | continue;
144 | }
145 | // System.out.println("Accepted " + child);
146 |
147 | if (child.isFile()) {
148 | if (!f.e(child, Cols.join(relPath, File.separator))) return false;
149 | } else {
150 | relPath.add(child.getName());
151 | if (!eachFileInDir(child, f, relPath, exclude)) return false;
152 | relPath.remove(relPath.size() - 1);
153 | }
154 | }
155 | return true;
156 | }
157 | */
158 | }
159 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/JarLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.jar.JarFile;
6 | import java.util.zip.ZipEntry;
7 |
8 | import com.github.msx80.wiseloader.BytesLoader;
9 |
10 | public class JarLoader implements BytesLoader {
11 | File file;
12 | JarFile jarFile;
13 |
14 | public JarLoader(File file) {
15 | this.file = file;
16 | try {
17 | this.jarFile = new JarFile(file);
18 | } catch (IOException e) {
19 | throw new RuntimeException(e);
20 | }
21 | }
22 |
23 | @Override
24 | public byte[] loadFile(String filePath) throws Exception {
25 | ZipEntry entry = jarFile.getJarEntry(filePath);
26 | if (entry == null) {
27 | if(filePath.startsWith("/"))
28 | {
29 | // retry without leading slash
30 | return loadFile(filePath.substring(1));
31 | }
32 | else
33 | {
34 | return null;
35 | }
36 | }
37 | try {
38 | return FileUtil.readData(jarFile.getInputStream(entry));
39 | } catch (IOException e) {
40 | throw new RuntimeException(e);
41 | }
42 | }
43 |
44 |
45 | public byte[] loadFileInternal(String cf) {
46 | ZipEntry entry = jarFile.getJarEntry(cf);
47 | if (entry == null) {
48 |
49 | return null;
50 |
51 | }
52 | try {
53 | return FileUtil.readData(jarFile.getInputStream(entry));
54 | } catch (IOException e) {
55 | throw new RuntimeException(e);
56 | }
57 | }
58 |
59 | @Override
60 | protected void finalize() throws Throwable {
61 | FileUtil.close(jarFile);
62 | super.finalize();
63 | }
64 |
65 | public void close() {
66 | FileUtil.close(jarFile);
67 |
68 | }
69 |
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/MultiBytesLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.List;
6 |
7 | import com.github.msx80.wiseloader.BytesLoader;
8 |
9 | public class MultiBytesLoader implements BytesLoader
10 | {
11 | List loaders = new ArrayList<>();
12 |
13 | public MultiBytesLoader(BytesLoader... loaders) {
14 | this.loaders.addAll(Arrays.asList(loaders));
15 | }
16 |
17 | public MultiBytesLoader(List loaders) {
18 | this.loaders.addAll(loaders);
19 | }
20 |
21 | public void add(BytesLoader loader)
22 | {
23 | this.loaders.add(loader);
24 | }
25 |
26 | @Override
27 | public byte[] loadFile(String name) throws Exception {
28 | for (BytesLoader loader : loaders)
29 | {
30 | byte[] data = loader.loadFile(name);
31 | if (data!= null) {
32 | return data;
33 | }
34 | }
35 | return null;
36 | }
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/ClassFolder.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | import java.util.List;
4 | import java.util.Set;
5 |
6 | import javax.tools.JavaFileObject;
7 | import javax.tools.JavaFileObject.Kind;
8 |
9 | public interface ClassFolder {
10 |
11 | List list(String path, Set kinds);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/CompilationError.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | public class CompilationError extends RuntimeException {
4 |
5 | /**
6 | *
7 | */
8 | private static final long serialVersionUID = 3724227920727781437L;
9 |
10 | public CompilationError() {
11 | }
12 |
13 | public CompilationError(String message) {
14 | super(message);
15 | }
16 |
17 | public CompilationError(Throwable cause) {
18 | super(cause);
19 | }
20 |
21 | public CompilationError(String message, Throwable cause) {
22 | super(message, cause);
23 | }
24 |
25 | public CompilationError(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
26 | super(message, cause, enableSuppression, writableStackTrace);
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/CompilingLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.Map.Entry;
9 |
10 | import javax.tools.Diagnostic;
11 | import javax.tools.DiagnosticListener;
12 | import javax.tools.JavaCompiler;
13 | import javax.tools.JavaFileObject;
14 | import javax.tools.StandardJavaFileManager;
15 | import javax.tools.ToolProvider;
16 |
17 | import com.github.msx80.wiseloader.BytesLoader;
18 | import com.github.msx80.wiseloader.WhitelistClassLoader;
19 |
20 | /**
21 | * A BytesLoader that tries to compile a set of java sources,
22 | * to provide the required bytecode.
23 | *
24 | */
25 | public class CompilingLoader implements BytesLoader {
26 |
27 | private Map files;
28 | private ClassFolder[] classFolders;
29 |
30 | public CompilingLoader( Map javaSources, Map extraResourceFiles, ClassFolder... classFolders)
31 | {
32 | this.files = new HashMap<>();
33 | this.classFolders = classFolders;
34 | files.putAll(extraResourceFiles);
35 | files.putAll(compileFromJava(javaSources));
36 | }
37 |
38 | boolean errors;
39 |
40 | @Override
41 | public byte[] loadFile(String filePath) {
42 |
43 | byte[] b = files.get(filePath);
44 |
45 | return b;
46 | }
47 |
48 | private Map compileFromJava(Map sources)
49 | {
50 |
51 | JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
52 | if(compiler==null) throw new CompilationError("JavaCompiler not found. Not running on a jdk?");
53 |
54 | Map javaFileObjects = new HashMap();
55 |
56 | for (Entry e : sources.entrySet()) {
57 | javaFileObjects.put(e.getKey(), new JavaSourceFromString(e.getKey(), e.getValue()));
58 | }
59 |
60 | List> errors = new ArrayList>();
61 |
62 | Collection compilationUnits = javaFileObjects.values();
63 |
64 | StandardJavaFileManager s_standardJavaFileManager = compiler.getStandardFileManager(null, null, null);
65 | CustomJavaFileManager s_fileManager = new CustomJavaFileManager(s_standardJavaFileManager, classFolders);
66 |
67 | compiler.getTask(null, s_fileManager, new DiagnosticListener() {
68 | @Override
69 | public void report(Diagnostic extends JavaFileObject> diagnostic) {
70 | if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
71 | errors.add(diagnostic);
72 | }
73 | }
74 | }, null, null, compilationUnits).call();
75 |
76 | if (!errors.isEmpty()) {
77 | throw new CompilationError(errors.get(0).toString());
78 | }
79 |
80 | Map comp = s_fileManager.getAllBuffers();
81 | Map result = new HashMap<>();
82 |
83 | for (String cn : comp.keySet()) {
84 | System.out.println("Compiled: "+cn);
85 | result.put(WhitelistClassLoader.toFilePath(cn), comp.get(cn));
86 | }
87 |
88 | return result;
89 | }
90 |
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/CustomJavaFileManager.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.OutputStream;
8 | import java.net.URI;
9 | import java.util.ArrayList;
10 | import java.util.Iterator;
11 | import java.util.LinkedHashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.Set;
15 |
16 | import javax.tools.FileObject;
17 | import javax.tools.JavaFileManager;
18 | import javax.tools.JavaFileObject;
19 | import javax.tools.JavaFileObject.Kind;
20 | import javax.tools.SimpleJavaFileObject;
21 | import javax.tools.StandardJavaFileManager;
22 | import javax.tools.StandardLocation;
23 |
24 | class CustomJavaFileManager implements JavaFileManager {
25 | private final StandardJavaFileManager fileManager;
26 | private final Map buffers = new LinkedHashMap();
27 | private ClassFolder[] folders;
28 |
29 | CustomJavaFileManager(StandardJavaFileManager fileManager, ClassFolder... folders) {
30 | this.fileManager = fileManager;
31 | this.folders = folders;
32 | }
33 |
34 |
35 |
36 | public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
37 | if(location.equals(StandardLocation.PLATFORM_CLASS_PATH))
38 | {
39 | // for platform stuff, lets call the parent manager
40 | return fileManager.list(location, packageName, kinds, recurse);
41 | }
42 | else
43 | {
44 | // search within provided ClassFolders
45 | List res = new ArrayList<>();
46 | String path = packageName.replace('.', '/');
47 | for (ClassFolder cf : folders) {
48 | res.addAll(cf.list(path, kinds));
49 | }
50 | return res;
51 | }
52 | }
53 |
54 | public String inferBinaryName(Location location, JavaFileObject file) {
55 | if(file instanceof Inferable)
56 | {
57 | return ((Inferable)file).inferBinaryName();
58 | }
59 | return fileManager.inferBinaryName(location, file);
60 | }
61 |
62 | public boolean isSameFile(FileObject a, FileObject b) {
63 | return fileManager.isSameFile(a, b);
64 | }
65 |
66 | public boolean handleOption(String current, Iterator remaining) {
67 | return fileManager.handleOption(current, remaining);
68 | }
69 |
70 | public boolean hasLocation(Location location) {
71 | return fileManager.hasLocation(location);
72 | }
73 |
74 | public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
75 | if (location == StandardLocation.CLASS_OUTPUT && buffers.containsKey(className) && kind == Kind.CLASS) {
76 | final byte[] bytes = buffers.get(className).toByteArray();
77 | return new SimpleJavaFileObject(URI.create(className), kind) {
78 |
79 | public InputStream openInputStream() {
80 | return new ByteArrayInputStream(bytes);
81 | }
82 | };
83 | }
84 | JavaFileObject jfo = fileManager.getJavaFileForInput(location, className, kind);
85 | System.out.println("Returning java file: "+jfo);
86 | return jfo;
87 | }
88 |
89 | public JavaFileObject getJavaFileForOutput(Location location, final String className, Kind kind, FileObject sibling) throws IOException {
90 | return new SimpleJavaFileObject(URI.create(className), kind) {
91 |
92 | public OutputStream openOutputStream() {
93 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
94 | buffers.put(className, baos);
95 | return baos;
96 | }
97 | };
98 | }
99 |
100 | public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
101 | return fileManager.getFileForInput(location, packageName, relativeName);
102 | }
103 |
104 | public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
105 | return fileManager.getFileForOutput(location, packageName, relativeName, sibling);
106 | }
107 |
108 | public void flush() throws IOException {
109 | // Do nothing
110 | }
111 |
112 | public void close() throws IOException {
113 | fileManager.close();
114 | }
115 |
116 | public int isSupportedOption(String option) {
117 | return fileManager.isSupportedOption(option);
118 | }
119 |
120 | public void clearBuffers() {
121 | buffers.clear();
122 | }
123 |
124 | public Map getAllBuffers() {
125 | Map ret = new LinkedHashMap(buffers.size() * 2);
126 | for (Map.Entry entry : buffers.entrySet()) {
127 | ret.put(entry.getKey(), entry.getValue().toByteArray());
128 | }
129 | return ret;
130 | }
131 |
132 | public ClassLoader getClassLoader(Location location) {
133 | //throw new UnsupportedOperationException();
134 | return new ClassLoader()
135 | {
136 | protected Class> findClass(String name) throws ClassNotFoundException {
137 | throw new UnsupportedOperationException();
138 | }
139 | protected Class> loadClass(String name, boolean resolve)
140 | throws ClassNotFoundException
141 | {
142 | throw new UnsupportedOperationException();
143 | }
144 | public Class> loadClass(String name) throws ClassNotFoundException
145 | {
146 | throw new UnsupportedOperationException();
147 | }
148 | };
149 | }
150 |
151 | //
152 |
153 | /*
154 |
155 | RESTORE THESE TO MAKE IT RUN ON JAVA 9+
156 |
157 | @Override
158 | public Iterable> listLocationsForModules(Location location) throws IOException {
159 | return fileManager.listLocationsForModules(location);
160 | }
161 |
162 | @Override
163 | public String inferModuleName(Location location) throws IOException {
164 |
165 | return fileManager.inferModuleName(location);
166 | }
167 |
168 | */
169 |
170 | }
171 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/Inferable.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | public interface Inferable {
4 |
5 | String inferBinaryName();
6 |
7 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/JarFileObject.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.io.Reader;
7 | import java.io.Writer;
8 | import java.net.URI;
9 |
10 | import javax.tools.SimpleJavaFileObject;
11 |
12 | import net.lingala.zip4j.ZipFile;
13 | import net.lingala.zip4j.model.FileHeader;
14 |
15 | public class JarFileObject extends SimpleJavaFileObject implements Inferable {
16 |
17 | private ZipFile zip;
18 | private FileHeader f;
19 |
20 | public JarFileObject(ZipFile zip, FileHeader f) {
21 | super(URI.create("jar:///"+(zip.getFile().toString().replace('\\', '/'))+"!"+f.getFileName()), Kind.CLASS);
22 | this.zip = zip;
23 | this.f = f;
24 | }
25 |
26 | @Override
27 | public InputStream openInputStream() throws IOException {
28 |
29 | return zip.getInputStream(f);
30 | }
31 |
32 | @Override
33 | public OutputStream openOutputStream() throws IOException {
34 | throw new UnsupportedOperationException();
35 | }
36 |
37 | @Override
38 | public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
39 | throw new UnsupportedOperationException();
40 | }
41 |
42 | @Override
43 | public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
44 | throw new UnsupportedOperationException();
45 | }
46 |
47 | @Override
48 | public Writer openWriter() throws IOException {
49 | throw new UnsupportedOperationException();
50 | }
51 |
52 | @Override
53 | public String inferBinaryName() {
54 | String s = f.getFileName();
55 | s = s.substring(0, s.length()-6); // .class
56 | s = s.replace('/', '.');
57 | return s;
58 | }
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/JarFolder.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | import java.io.File;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.Set;
7 |
8 | import javax.tools.JavaFileObject;
9 | import javax.tools.JavaFileObject.Kind;
10 |
11 | import net.lingala.zip4j.ZipFile;
12 | import net.lingala.zip4j.exception.ZipException;
13 | import net.lingala.zip4j.model.FileHeader;
14 |
15 | public class JarFolder implements ClassFolder {
16 |
17 | ZipFile zip;
18 | private List headers;
19 |
20 | public JarFolder(File f)
21 | {
22 | zip = new ZipFile(f);
23 | try {
24 | headers = zip.getFileHeaders();
25 | } catch (ZipException e) {
26 | throw new RuntimeException(e);
27 | }
28 | }
29 |
30 | @Override
31 | public List list(String path, Set kinds) {
32 | List res = new ArrayList();
33 | if(kinds.contains(Kind.CLASS))
34 | {
35 | for (FileHeader f : headers) {
36 | if(f.getFileName().startsWith(path) && f.getFileName().endsWith(".class"))
37 | {
38 | if(!f.isDirectory())
39 | {
40 | String rem = f.getFileName().substring(path.length()+1);
41 | if(!rem.contains("/"))
42 | {
43 | res.add(new JarFileObject(zip, f));
44 | }
45 | }
46 | }
47 | }
48 | }
49 | return res;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/github/msx80/wiseloader/loaders/compiler/JavaSourceFromString.java:
--------------------------------------------------------------------------------
1 | package com.github.msx80.wiseloader.loaders.compiler;
2 |
3 | import java.net.URI;
4 |
5 | import javax.tools.SimpleJavaFileObject;
6 |
7 | class JavaSourceFromString extends SimpleJavaFileObject {
8 | /**
9 | * The source code of this "file".
10 | */
11 | private final String code;
12 |
13 | /**
14 | * Constructs a new JavaSourceFromString.
15 | *
16 | * @param name the name of the compilation unit represented by this file object
17 | * @param code the source code for the compilation unit represented by this file object
18 | */
19 | JavaSourceFromString(String name, String code) {
20 | super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension),
21 | Kind.SOURCE);
22 | this.code = code;
23 | }
24 |
25 | @SuppressWarnings("RefusedBequest")
26 | @Override
27 | public CharSequence getCharContent(boolean ignoreEncodingErrors) {
28 | return code;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------