This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new c0b85019 JEXL-466: ensure JexlEngine is thread-local accessible when 
creating TemplateScript and TemplateExpressions;
c0b85019 is described below

commit c0b850195aab78c392f7c809ec3afbe50f799961
Author: Henrib <[email protected]>
AuthorDate: Fri Jul 24 15:32:31 2026 +0200

    JEXL-466: ensure JexlEngine is thread-local accessible when creating 
TemplateScript and TemplateExpressions;
---
 .../org/apache/commons/jexl3/internal/Engine.java  | 19 +++---
 .../commons/jexl3/internal/TemplateEngine.java     |  4 ++
 .../commons/jexl3/internal/TemplateScript.java     | 38 ++++++-----
 .../org/apache/commons/jexl3/Issues400Test.java    | 77 ++++++++++++++++++++++
 4 files changed, 114 insertions(+), 24 deletions(-)

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 d26a015e..2de56103 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -345,7 +345,7 @@ public class Engine extends JexlEngine implements 
JexlUberspect.ConstantResolver
      * The set of caches created by this engine.
      * <p>Caches are soft-referenced by the engine so they can be cleaned on 
class loader change.</p>
      */
-    protected final MetaCache metaCache;
+    final MetaCache metaCache;
 
     /**
      * Creates an engine with default arguments.
@@ -847,7 +847,7 @@ public class Engine extends JexlEngine implements 
JexlUberspect.ConstantResolver
             if (parsing.compareAndSet(false, true)) {
                 synchronized (parsing) {
                     try {
-                        // lets parse
+                        // let's parse
                         script = parser.parse(ninfo, features, src, scope);
                     } finally {
                         // no longer in use
@@ -862,7 +862,7 @@ public class Engine extends JexlEngine implements 
JexlUberspect.ConstantResolver
                 cache.put(source, script);
             }
         } finally {
-            // restore thread local engine
+            // restore thread local engine (most likely null)
             putThreadEngine(se);
         }
         return script;
@@ -1018,15 +1018,18 @@ public class Engine extends JexlEngine implements 
JexlUberspect.ConstantResolver
     }
 
     /**
-     * Swaps the current thread local engine.
+     * Swaps the current thread local engine if it differs from argument.
      *
      * @param jexl The engine or null
      * @return The previous thread local engine
      */
-    protected JexlEngine putThreadEngine(final JexlEngine jexl) {
-        final JexlEngine pjexl = ENGINE.get();
-        ENGINE.set(jexl);
-        return pjexl;
+    protected static JexlEngine putThreadEngine(final JexlEngine jexl) {
+        final JexlEngine engine = ENGINE.get();
+        if (engine != jexl) {
+            ENGINE.set(jexl);
+            return engine;
+        }
+        return jexl;
     }
 
     /**
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java 
b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
index cd4a6a07..36d0bba7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -29,6 +29,7 @@ import java.util.Set;
 
 import org.apache.commons.jexl3.JexlCache;
 import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlInfo;
@@ -917,6 +918,7 @@ public final class TemplateEngine extends JxltEngine {
         final JexlFeatures features = noscript ? jexl.expressionFeatures : 
jexl.scriptFeatures;
         // do not cache interpolation expression, they are stored in AST node
         final boolean cached = cache != null && expression.length() < 
jexl.cacheThreshold;
+        final JexlEngine je = Engine.putThreadEngine(jexl);
         try {
             if (!cached) {
                 stmt = parseExpression(info, expression, scope);
@@ -932,6 +934,8 @@ public final class TemplateEngine extends JxltEngine {
             }
         } catch (final JexlException xjexl) {
             xuel = new Exception(xjexl.getInfo(), "failed to parse '" + 
expression + "'", xjexl);
+        } finally {
+            Engine.putThreadEngine(je);
         }
         if (xuel != null) {
             if (!jexl.isSilent()) {
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java 
b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
index 92550b1e..5653797b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -25,6 +25,7 @@ import java.util.Objects;
 import java.util.Set;
 
 import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
 import org.apache.commons.jexl3.JexlInfo;
 import org.apache.commons.jexl3.JexlOptions;
@@ -213,22 +214,27 @@ public final class TemplateScript implements 
JxltEngine.Template {
                 verbatims += 1;
             }
         }
-        final String scriptSource = callerScript(blocks);
-        // allow lambda defining params
-        final JexlInfo info = jexlInfo == null ? jexl.createInfo() : jexlInfo;
-        final Scope scope = parms == null ? null : new Scope(null, parms);
-        final JexlInfo templateInfo = 
jexl.scriptFeatures.isIgnoreTemplatePrefix()
-            ? new TemplateInfo(info.at(1, 1), Collections.singleton(prefix))
-            : info.at(1, 1);
-        final ASTJexlScript callerScript = jexl.jxltParse(templateInfo, false, 
scriptSource, scope).script();
-        // seek the map of expression number to scope so we can parse Unified
-        // expression blocks with the appropriate symbols
-        final JexlNode.Info[] callSites = new JexlNode.Info[verbatims];
-        collectPrintScope(callerScript.script(), callSites);
-        // create the expressions from the blocks
-        this.exprs = calleeScripts(scope, blocks, callSites);
-        this.script = callerScript;
-        this.source = blocks;
+        final JexlEngine je = Engine.putThreadEngine(jexl);
+        try {
+            final String scriptSource = callerScript(blocks);
+            // allow lambda defining params
+            final JexlInfo info = jexlInfo == null ? jexl.createInfo() : 
jexlInfo;
+            final Scope scope = parms == null ? null : new Scope(null, parms);
+            final JexlInfo templateInfo = 
jexl.scriptFeatures.isIgnoreTemplatePrefix()
+                ? new TemplateInfo(info.at(1, 1), 
Collections.singleton(prefix))
+                : info.at(1, 1);
+            final ASTJexlScript callerScript = jexl.jxltParse(templateInfo, 
false, scriptSource, scope).script();
+            // seek the map of expression number to scope so we can parse 
Unified
+            // expression blocks with the appropriate symbols
+            final JexlNode.Info[] callSites = new JexlNode.Info[verbatims];
+            collectPrintScope(callerScript.script(), callSites);
+            // create the expressions from the blocks
+            this.exprs = calleeScripts(scope, blocks, callSites);
+            this.script = callerScript;
+            this.source = blocks;
+        } finally {
+            Engine.putThreadEngine(je);
+        }
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java 
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index a39e4ffc..4c8500ec 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -1258,5 +1258,82 @@ public class Issues400Test {
         pw.flush();
         assertTrue(sw.toString().contains("hello"));
     }
+
+    @Test
+    void test462a() {
+        // JEXL-462: under RESTRICTED, java.io.PrintWriter and 
java.util.Formatter are reachable and their methods are allowed,
+        // but their constructors are denied - they cannot be instantiated 
from a script.
+        final JexlEngine jexl = new 
JexlBuilder().silent(false).permissions(RESTRICTED).create();
+        for (final String src : new String[] {
+            "new('java.io.PrintWriter', '/tmp/pwn0.php')",
+            "import java.io.PrintWriter; new PrintWriter('/tmp/pwn1.php')"}) {
+            final JexlScript script = jexl.createScript(src);
+            final JexlException.Method xctor = 
assertThrows(JexlException.Method.class,
+                () -> script.execute(null), () -> "PrintWriter::new should be 
denied: " + src);
+            assertTrue(xctor.getMethod().contains("PrintWriter"));
+        }
+        // a PrintWriter method (println) is allowed on a provided instance
+        StringWriter sw = new StringWriter();
+        final java.io.PrintWriter pw = new java.io.PrintWriter(sw);
+        jexl.createScript("p.println('hello')", "p").execute(null, pw);
+        pw.flush();
+        assertTrue(sw.toString().contains("hello"));
+        for (final String src : new String[] {
+          "new('java.util.Formatter', '/tmp/pwn2.php')",
+          "import java.util.Formatter; new Formatter('/tmp/pwn3.php')" }) {
+            final JexlScript script = jexl.createScript(src);
+            final JexlException.Method xctor = 
assertThrows(JexlException.Method.class,
+              () -> script.execute(null), () -> "Formatter::new should be 
denied: " + src);
+            assertTrue(xctor.getMethod().contains("Formatter"));
+        }
+
+        // a PrintWriter method (println) is allowed on a provided instance
+        sw = new StringWriter();
+        final java.util.Formatter pf = new java.util.Formatter(sw);
+        jexl.createScript("pf.format('%s', 'bonjour')", "pf").execute(null, 
pf);
+        assertTrue(sw.toString().contains("bonjour"));
+    }
+
+    @Test
+    void test463() {
+        final JexlEngine jexl = new JexlBuilder()
+          .silent(false)
+          .features(JexlFeatures.createDefault())
+          .permissions(RESTRICTED)
+          .create();
+        String src = "addOne  = (f) -> {\n return f + 1;\n}";
+        JexlScript script = jexl.createScript(src);
+        assertNotNull(script);
+        JexlContext ctxt = new MapContext();
+        Object result = script.execute(ctxt);
+        assertNotNull(result);
+        JexlScript rs = (JexlScript) result;
+        result = rs.execute(ctxt, 1);
+        assertEquals(2, result);
+    }
+
+    @Test
+    void test466() {
+        final JexlEngine jexl = new JexlBuilder()
+          .silent(false)
+          .features(JexlFeatures.createDefault())
+          .permissions(RESTRICTED)
+          .create();
+        JxltEngine jxlt = jexl.createJxltEngine();
+
+        String src = "$$ const a = 'a';\n" + "$$ const b = `${a}` \n" + 
"b:${b}";
+        JxltEngine.Template template = jxlt.createTemplate(src);
+        StringWriter writer = new StringWriter();
+        template.evaluate(null, writer);
+        assertEquals("b:a", writer.toString());
+
+        String expr = "forty-two: ${b + a}";
+        JexlContext ctxt = new MapContext();
+        ctxt.set("b", 20);
+        ctxt.set("a", 22);
+        JxltEngine.Expression expression = jxlt.createExpression(expr);
+        Object value = expression.evaluate(ctxt);
+        assertEquals("forty-two: 42", value.toString());
+    }
 }
 

Reply via email to