This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch JEXL-381
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/JEXL-381 by this push:
new 8760868c JEXL-381: added import/namespace pragma feature to
enable/disable syntax; - added JexlUberspect#getClassByName that verifies
permissions, use it when resolving namespaces; - updated restricted permissions
set based on Dmitri feedback;
8760868c is described below
commit 8760868c92a542d939569a5f3b627775df22b73d
Author: henrib <[email protected]>
AuthorDate: Mon Nov 7 14:58:17 2022 +0100
JEXL-381: added import/namespace pragma feature to enable/disable syntax;
- added JexlUberspect#getClassByName that verifies permissions, use it when
resolving namespaces;
- updated restricted permissions set based on Dmitri feedback;
---
.../org/apache/commons/jexl3/JexlFeatures.java | 85 ++++++++++++++++++----
.../org/apache/commons/jexl3/internal/Engine.java | 29 +++-----
.../jexl3/internal/introspection/Introspector.java | 12 +--
.../internal/introspection/SandboxUberspect.java | 5 ++
.../jexl3/introspection/JexlPermissions.java | 6 +-
.../commons/jexl3/introspection/JexlUberspect.java | 24 ++++--
.../apache/commons/jexl3/parser/JexlParser.java | 16 +++-
.../java/org/apache/commons/jexl3/PragmaTest.java | 33 ++++++++-
.../internal/introspection/PermissionsTest.java | 5 +-
.../commons/jexl3/jexl342/ReferenceUberspect.java | 4 +
10 files changed, 170 insertions(+), 49 deletions(-)
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 124468fb..2b692fd1 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -60,7 +60,7 @@ public final class JexlFeatures {
"register", "reserved variable", "local variable", "assign/modify",
"global assign/modify", "array reference", "create instance", "loop",
"function",
"method call", "set/map/array literal", "pragma", "annotation",
"script", "lexical", "lexicalShade",
- "thin-arrow", "fat-arrow"
+ "thin-arrow", "fat-arrow", "namespace pragma", "import pragma"
};
/** Registers feature ordinal. */
private static final int REGISTER = 0;
@@ -98,24 +98,35 @@ public final class JexlFeatures {
public static final int THIN_ARROW = 16;
/** Fat-arrow lambda syntax. */
public static final int FAT_ARROW = 17;
+ /** Namespace pragma feature ordinal. */
+ public static final int NS_PRAGMA = 18;
+ /** Import pragma feature ordinal. */
+ public static final int IMPORT_PRAGMA = 19;
+ /**
+ * The default features flag mask.
+ */
+ private static final long DEFAULT_FEATURES =
+ (1L << LOCAL_VAR)
+ | (1L << SIDE_EFFECT)
+ | (1L << SIDE_EFFECT_GLOBAL)
+ | (1L << ARRAY_REF_EXPR)
+ | (1L << NEW_INSTANCE)
+ | (1L << LOOP)
+ | (1L << LAMBDA)
+ | (1L << METHOD_CALL)
+ | (1L << STRUCTURED_LITERAL)
+ | (1L << PRAGMA)
+ | (1L << ANNOTATION)
+ | (1L << SCRIPT)
+ | (1L << THIN_ARROW)
+ | (1L << NS_PRAGMA)
+ | (1L << IMPORT_PRAGMA);
/**
* Creates an all-features-enabled instance.
*/
public JexlFeatures() {
- flags = (1L << LOCAL_VAR)
- | (1L << SIDE_EFFECT)
- | (1L << SIDE_EFFECT_GLOBAL)
- | (1L << ARRAY_REF_EXPR)
- | (1L << NEW_INSTANCE)
- | (1L << LOOP)
- | (1L << LAMBDA)
- | (1L << METHOD_CALL)
- | (1L << STRUCTURED_LITERAL)
- | (1L << PRAGMA)
- | (1L << ANNOTATION)
- | (1L << SCRIPT)
- | (1L << THIN_ARROW);
+ flags = DEFAULT_FEATURES;
reservedNames = Collections.emptySet();
nameSpaces = TEST_STR_FALSE;
}
@@ -489,16 +500,60 @@ public final class JexlFeatures {
*/
public JexlFeatures pragma(final boolean flag) {
setFeature(PRAGMA, flag);
+ if (!flag) {
+ setFeature(NS_PRAGMA, false);
+ setFeature(IMPORT_PRAGMA, false);
+ }
return this;
}
/**
- * @return true if pragma are enabled, false otherwise
+ * @return true if namespace pragma are enabled, false otherwise
*/
public boolean supportsPragma() {
return getFeature(PRAGMA);
}
+ /**
+ * Sets whether namespace pragma constructs are enabled.
+ * <p>
+ * When disabled, parsing a script/expression using syntactic namespace
pragma constructs
+ * (#pragma jexl.namespace....) will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures namespacePragma(final boolean flag) {
+ setFeature(NS_PRAGMA, flag);
+ return this;
+ }
+
+ /**
+ * @return true if namespace pragma are enabled, false otherwise
+ */
+ public boolean supportsNamespacePragma() {
+ return getFeature(NS_PRAGMA);
+ }
+ /**
+ * Sets whether import pragma constructs are enabled.
+ * <p>
+ * When disabled, parsing a script/expression using syntactic import
pragma constructs
+ * (#pragma jexl.import....) will throw a parsing exception.
+ * @param flag true to enable, false to disable
+ * @return this features instance
+ */
+ public JexlFeatures importPragma(final boolean flag) {
+ setFeature(IMPORT_PRAGMA, flag);
+ return this;
+ }
+
+ /**
+ * @return true if import pragma are enabled, false otherwise
+ */
+ public boolean supportsImportPragma() {
+ return getFeature(IMPORT_PRAGMA);
+ }
+
+
/**
* Sets whether annotation constructs are enabled.
* <p>
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index 54630f58..2e384983 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -56,6 +56,10 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
+import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_IMPORT;
+import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_JEXLNS;
+import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_OPTIONS;
+
/**
* A JexlEngine implementation.
* @since 2.0
@@ -79,18 +83,6 @@ public class Engine extends JexlEngine {
/** Non-instantiable. */
private UberspectHolder() {}
}
- /**
- * The name of the options pragma.
- */
- protected static final String PRAGMA_OPTIONS = "jexl.options";
- /**
- * The prefix of a namespace pragma.
- */
- protected static final String PRAGMA_JEXLNS = "jexl.namespace.";
- /**
- * The prefix of an import pragma.
- */
- protected static final String PRAGMA_IMPORT = "jexl.import";
/**
* The Log to which all JexlEngine messages will be logged.
*/
@@ -353,7 +345,7 @@ public class Engine extends JexlEngine {
/**
* Extracts the engine evaluation options from context if available, the
engine
* options otherwise.
- * <p>If the context is a options handle and the handled options shared
instance flag
+ * <p>If the context is an options handle and the handled options shared
instance flag
* is false, this method creates a copy of the options making them
immutable during execution.
* @param context the context
* @return the options if any
@@ -457,15 +449,16 @@ public class Engine extends JexlEngine {
if (value instanceof String) {
// jexl.namespace.***
final String nsname =
key.substring(PRAGMA_JEXLNS.length());
- if (nsname != null && !nsname.isEmpty()) {
+ if (!nsname.isEmpty()) {
if (ns == null) {
ns = new HashMap<>(functions);
}
final String nsclass = value.toString();
- try {
- ns.put(nsname,
uberspect.getClassLoader().loadClass(nsclass));
- } catch (final ClassNotFoundException e) {
- ns.put(nsname, nsclass);
+ Class<?> clazz = uberspect.getClassByName(nsclass);
+ if (clazz == null) {
+ logger.warn(key + ": unable to find class " +
nsclass);
+ } else {
+ ns.put(nsname, clazz);
}
}
}
diff --git
a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index 869c5405..761d1af2 100644
---
a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++
b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -62,7 +62,7 @@ public final class Introspector {
/**
* the logger.
*/
- protected final Log logger;
+ private final Log logger;
/**
* The class loader used to solve constructors if needed.
*/
@@ -78,15 +78,15 @@ public final class Introspector {
/**
* Holds the method maps for the classes we know about, keyed by Class.
*/
- private final Map<Class<?>, ClassMap> classMethodMaps = new
HashMap<Class<?>, ClassMap>();
+ private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<>();
/**
* Holds the map of classes ctors we know about as well as unknown ones.
*/
- private final Map<MethodKey, Constructor<?>> constructorsMap = new
HashMap<MethodKey, Constructor<?>>();
+ private final Map<MethodKey, Constructor<?>> constructorsMap = new
HashMap<>();
/**
* Holds the set of classes we have introspected.
*/
- private final Map<String, Class<?>> constructibleClasses = new
HashMap<String, Class<?>>();
+ private final Map<String, Class<?>> constructibleClasses = new HashMap<>();
/**
* Create the introspector.
@@ -116,7 +116,8 @@ public final class Introspector {
*/
public Class<?> getClassByName(final String className) {
try {
- return Class.forName(className, false, loader);
+ Class<?> clazz = Class.forName(className, false, loader);
+ return permissions.allow(clazz)? clazz : null;
} catch (final ClassNotFoundException xignore) {
return null;
}
@@ -279,7 +280,6 @@ public final class Introspector {
+ cname + "."
+ key.debugString(), xnotfound);
}
- ctor = null;
} catch (final MethodKey.AmbiguousException xambiguous) {
if (logger != null && xambiguous.isSevere() &&
logger.isInfoEnabled()) {
logger.info("ambiguous constructor invocation: "
diff --git
a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
index c2a98ce5..314b8acb 100644
---
a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
+++
b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
@@ -68,6 +68,11 @@ public final class SandboxUberspect implements JexlUberspect
{
return uberspect.getVersion();
}
+ @Override
+ public Class<?> getClassByName(final String className) {
+ return uberspect.getClassByName(className);
+ }
+
@Override
public JexlMethod getConstructor(final Object ctorHandle, final Object...
args) {
final String className;
diff --git
a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
index 97243680..ce9b95db 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
@@ -223,7 +223,9 @@ public interface JexlPermissions {
"org.apache.commons.jexl3.*",
"org.apache.commons.jexl3 { JexlBuilder {} }",
"org.apache.commons.jexl3.internal { Engine {} }",
- "java.lang { Runtime {} System {} ProcessBuilder {} Class {} }",
+ "java.lang { Runtime{} System{} ProcessBuilder{} Process{}" +
+ " RuntimePermission{} SecurityManager{}" +
+ " Thread{} ThreadGroup{} Class{} }",
"java.lang.annotation {}",
"java.lang.instrument {}",
"java.lang.invoke {}",
@@ -231,7 +233,7 @@ public interface JexlPermissions {
"java.lang.ref {}",
"java.lang.reflect {}",
"java.net {}",
- "java.io { File { } }",
+ "java.io { File{} FileDescriptor{} }",
"java.nio { Path { } Paths { } Files { } }",
"java.rmi"
);
diff --git
a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
index 08a9b02f..009e8149 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
@@ -37,7 +37,7 @@ public interface JexlUberspect {
* Abstracts getting property setter and getter.
* <p>
* These are used through 'strategies' to solve properties; a strategy
orders a list of resolver types,
- * and each resolver type is tried in sequence; the first resolver that
discovers a non null {s,g}etter
+ * and each resolver type is tried in sequence; the first resolver that
discovers a non-null {s,g}etter
* stops the search.
*
* @see JexlResolver
@@ -115,7 +115,7 @@ public interface JexlUberspect {
/**
* A resolver types list tailored for POJOs, favors '.' over '[]'.
*/
- static final List<PropertyResolver> POJO =
Collections.unmodifiableList(Arrays.asList(
+ List<PropertyResolver> POJO = Collections.unmodifiableList(Arrays.asList(
JexlResolver.PROPERTY,
JexlResolver.MAP,
JexlResolver.LIST,
@@ -128,7 +128,7 @@ public interface JexlUberspect {
/**
* A resolver types list tailored for Maps, favors '[]' over '.'.
*/
- static final List<PropertyResolver> MAP =
Collections.unmodifiableList(Arrays.asList(
+ List<PropertyResolver> MAP = Collections.unmodifiableList(Arrays.asList(
JexlResolver.MAP,
JexlResolver.LIST,
JexlResolver.DUCK,
@@ -165,7 +165,7 @@ public interface JexlUberspect {
* If the operator is '[]' or if the operator is null and the object is a
map, use the MAP list of resolvers;
* Other cases use the POJO list of resolvers.
*/
- static final ResolverStrategy JEXL_STRATEGY = (op, obj) -> {
+ ResolverStrategy JEXL_STRATEGY = (op, obj) -> {
if (op == JexlOperator.ARRAY_GET) {
return MAP;
}
@@ -184,7 +184,7 @@ public interface JexlUberspect {
* <p>If the operator is '[]' or if the object is a map, use the MAP list
of resolvers.
* Otherwise, use the POJO list of resolvers.</p>
*/
- static final ResolverStrategy MAP_STRATEGY = (op, obj) -> {
+ ResolverStrategy MAP_STRATEGY = (op, obj) -> {
if (op == JexlOperator.ARRAY_GET) {
return MAP;
}
@@ -228,6 +228,20 @@ public interface JexlUberspect {
*/
int getVersion();
+ /**
+ * Seeks a class by name using this uberspect class-loader.
+ * @param className the class name
+ * @return the class instance or null if the class cannot be located by
this uberspect class loader or if
+ * permissions deny access to the class
+ */
+ default Class<?> getClassByName(final String className) {
+ try {
+ return Class.forName(className, false, getClassLoader());
+ } catch (ClassNotFoundException xignore) {
+ return null;
+ }
+ }
+
/**
* Returns a class constructor.
*
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index 157ae9c2..a86119dc 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -489,10 +489,18 @@ public abstract class JexlParser extends StringParser {
}
}
+ /**
+ * The name of the options pragma.
+ */
+ public static final String PRAGMA_OPTIONS = "jexl.options";
/**
* The prefix of a namespace pragma.
*/
- protected static final String PRAGMA_JEXLNS = "jexl.namespace.";
+ public static final String PRAGMA_JEXLNS = "jexl.namespace.";
+ /**
+ * The import pragma.
+ */
+ public static final String PRAGMA_IMPORT = "jexl.import";
/**
* Adds a pragma declaration.
@@ -503,12 +511,18 @@ public abstract class JexlParser extends StringParser {
if (!getFeatures().supportsPragma()) {
throwFeatureException(JexlFeatures.PRAGMA, getToken(0));
}
+ if (PRAGMA_IMPORT.equals(key) &&
!getFeatures().supportsImportPragma()) {
+ throwFeatureException(JexlFeatures.IMPORT_PRAGMA, getToken(0));
+ }
if (pragmas == null) {
pragmas = new TreeMap<>();
}
// declaring a namespace
Predicate<String> ns = getFeatures().namespaceTest();
if (ns != null && key.startsWith(PRAGMA_JEXLNS)) {
+ if (!getFeatures().supportsNamespacePragma()) {
+ throwFeatureException(JexlFeatures.NS_PRAGMA, getToken(0));
+ }
// jexl.namespace.***
final String nsname = key.substring(PRAGMA_JEXLNS.length());
if (!nsname.isEmpty()) {
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index c67fa067..4bd306aa 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -140,7 +140,7 @@ public class PragmaTest extends JexlTestCase {
}
@Test
- public void testNamespacePragmaValueSet() {
+ public void testImportPragmaValueSet() {
String src =
"#pragma jexl.import java.util\n"+
"#pragma jexl.import java.io\n"+
@@ -159,6 +159,37 @@ public class PragmaTest extends JexlTestCase {
Assert.assertEquals(src, parsed);
}
+ @Test
+ public void testImportPragmaDisabled() {
+ String src =
+ "#pragma jexl.import java.util\n"+
+ "#pragma jexl.import java.io\n"+
+ "#pragma jexl.import java.net\n"+
+ "42";
+ JexlFeatures features = new JexlFeatures();
+ features.importPragma(false);
+ final JexlEngine jexl = new JexlBuilder().features(features).create();
+ try {
+ final JexlScript script = jexl.createScript(src);
+ } catch(JexlException.Parsing xparse) {
+ Assert.assertTrue(xparse.getMessage().contains("import pragma"));
+ }
+ }
+ @Test
+ public void testNamespacePragmaDisabled() {
+ JexlFeatures features = new JexlFeatures();
+ features.namespacePragma(false);
+ final JexlEngine jexl = new JexlBuilder().features(features).create();
+ try {
+ final JexlScript src = jexl.createScript(
+ "#pragma jexl.namespace.sleeper " +
StaticSleeper.class.getName() + "\n"
+ + "sleeper:sleep(100);"
+ + "42");
+ Assert.fail("should have thrown syntax exception");
+ } catch(JexlException.Parsing xparse) {
+ Assert.assertTrue(xparse.getMessage().contains("namespace
pragma"));
+ }
+ }
@Test
@SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
public void testStaticNamespacePragma() {
diff --git
a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
index 6bf36179..da0eb7a8 100644
---
a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
+++
b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
@@ -26,6 +26,7 @@ import org.apache.commons.jexl3.JexlTestCase;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.jexl3.internal.introspection.nojexlpackage.Invisible;
import org.apache.commons.jexl3.introspection.JexlPermissions;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.junit.Assert;
import org.junit.Test;
@@ -166,7 +167,7 @@ public class PermissionsTest {
@Test
public void testParsePermissions0() throws Exception {
- String src = "java.lang { Runtime { exit(); exec(); } }";
+ String src = "java.lang { Runtime { exit(); exec(); } }\njava.net {
URL {} }";
Permissions p = (Permissions) JexlPermissions.parse(src);
Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
Assert.assertNotNull(nojexlmap);
@@ -178,6 +179,8 @@ public class PermissionsTest {
Method exec = getMethod(java.lang.Runtime.class,"exec");
Assert.assertNotNull(exec);
Assert.assertFalse(p.allow(exec));
+ JexlUberspect uber = new Uberspect(null, null, p);
+ Assert.assertNull(uber.getClassByName("java.net.URL"));
}
public static class Outer {
diff --git
a/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
b/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
index 8c44dfd5..b674334d 100644
--- a/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
+++ b/src/test/java/org/apache/commons/jexl3/jexl342/ReferenceUberspect.java
@@ -51,6 +51,10 @@ public class ReferenceUberspect implements JexlUberspect {
* The map resolver list strategy.
*/
private final List<PropertyResolver> mapStrategy;
+ @Override
+ public Class<?> getClassByName(final String className) {
+ return uberspect.getClassByName(className);
+ }
/**
* Constructor.