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

commit 2b027b4676fbd08c98f37b7014d3a158ef2bade1
Author: henrib <hen...@apache.org>
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.

Reply via email to