diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java index 73762a1234..6e94b6dc45 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java @@ -46,7 +46,7 @@ import com.jme3.texture.plugins.TGALoader; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -122,7 +122,7 @@ public default void removeClassLoader(ClassLoader loader) { */ @Deprecated public default List getClassLoaders() { - return new ArrayList<>(); + return Collections.emptyList(); } /** diff --git a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java index 252bb308e9..cee395764c 100644 --- a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java +++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java @@ -40,6 +40,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.logging.Level; @@ -198,9 +199,13 @@ public static Savable fromName(String className) * @throws ClassNotFoundException thrown if the class name is not in the classpath. * @throws SecurityException thrown if the filter rejects the class name. */ - public static Savable fromName(String className, SavableClassFilter classFilter) - throws ClassNotFoundException, IllegalAccessException, - InstantiationException, InvocationTargetException { + public static Savable fromName(String className, SavableClassFilter classFilter) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { + return fromName(className, classFilter, null); + } + + public static Savable fromName(String className, SavableClassFilter classFilter, Collection additionalClassLoaders) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { className = remapClass(className); if (classFilter == null) { throw new NullPointerException("classFilter"); @@ -209,7 +214,7 @@ public static Savable fromName(String className, SavableClassFilter classFilter) throw new SecurityException("Savable class rejected by filter: " + className); } - Constructor noArgConstructor = findNoArgConstructor(className); + Constructor noArgConstructor = findNoArgConstructor(className, additionalClassLoaders); noArgConstructor.setAccessible(true); try { return (Savable) noArgConstructor.newInstance(); @@ -226,6 +231,7 @@ public static Savable fromName(String className, SavableClassFilter classFilter) } } + /** * @deprecated use {@link #fromName(java.lang.String)} instead */ @@ -240,6 +246,9 @@ public static Savable fromName(String className, List loaders) thro String newClassName = remapClass(className); synchronized (loaders) { for (ClassLoader classLoader : loaders) { + if (classLoader == null) { + continue; + } final Class loadedClass; try { loadedClass = classLoader.loadClass(newClassName); @@ -263,12 +272,35 @@ public static Savable fromName(String className, List loaders) thro * @return the pre-existing constructor (not null) */ @SuppressWarnings("unchecked") - private static Constructor findNoArgConstructor(String className) + private static Constructor findNoArgConstructor(String className, Collection additionalClassLoaders) throws ClassNotFoundException, InstantiationException { - Class clazz = Class.forName(className); + Class clazz = null; + + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + if (additionalClassLoaders != null) { + for (ClassLoader classLoader : additionalClassLoaders) { + if (classLoader == null) { + continue; + } + try { + clazz = Class.forName(className, true, classLoader); + break; + } catch (ClassNotFoundException ex) { + // ignore + } + } + } + if (clazz == null) { + throw e; + } + } + if (!SavableClassUtil.isImplementingSavable(clazz)) { throw new InstantiationException("Class " + className + " does not implement Savable."); } + Constructor result; try { result = clazz.getDeclaredConstructor(); diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java index cc0f6bfb65..20d84cbb57 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java @@ -399,7 +399,7 @@ public Savable readObject(int id) { throw new IOException("Invalid J3O object data length: " + dataLength); } - Savable out = SavableClassUtil.fromName(bco.className, classFilter); + Savable out = SavableClassUtil.fromName(bco.className, classFilter, assetManager == null ? null : assetManager.getClassLoaders()); BinaryInputCapsule cap = new BinaryInputCapsule(this, out, bco); cap.setContent(dataArray, loc, loc+dataLength); diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java index 1d91ca903b..91bac4977e 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java @@ -34,6 +34,7 @@ import com.jme3.export.InputCapsule; import com.jme3.export.Savable; +import com.jme3.export.SavableClassFilter; import com.jme3.export.SavableClassUtil; import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; @@ -56,6 +57,7 @@ */ public class DOMInputCapsule implements InputCapsule { private static final Logger logger = Logger.getLogger(DOMInputCapsule.class .getName()); + private SavableClassFilter classFilter = SavableClassFilter.ACCEPT_ALL; private Document doc; private Element currentElement; @@ -85,6 +87,28 @@ public int getSavableVersion(Class desiredClass) { } } + /** + * Sets the policy used before this importer instantiates classes. + * The default accepts all classes for backward compatibility. + * Use a restrictive filter when loading assets from untrusted sources. + * + * @param classFilter the filter to apply + */ + public void setClassFilter(SavableClassFilter classFilter) { + if (classFilter == null) { + throw new NullPointerException("classFilter"); + } + this.classFilter = classFilter; + } + + /** + * @return the current class filter + */ + public SavableClassFilter getClassFilter() { + return classFilter; + } + + private Element findChildElement(String name) { if (currentElement == null) { return null; @@ -574,7 +598,7 @@ private Savable readSavableFromCurrentElement(Savable defVal) throws IOException Savable tmp = null; try { - tmp = SavableClassUtil.fromName(className); + tmp = SavableClassUtil.fromName(className, classFilter, importer.getAssetManager() == null ? null : importer.getAssetManager().getClassLoaders()); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new IOException(e); }