View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jexl3.scripting;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.BufferedReader;
28  import java.io.BufferedWriter;
29  import java.io.ByteArrayOutputStream;
30  import java.io.FileWriter;
31  import java.io.PrintStream;
32  import java.io.PrintWriter;
33  import java.io.Reader;
34  import java.io.StringReader;
35  import java.io.StringWriter;
36  import java.nio.file.Files;
37  import java.nio.file.Path;
38  import java.util.Arrays;
39  import java.util.List;
40  import java.util.Map;
41  
42  import javax.script.CompiledScript;
43  import javax.script.ScriptContext;
44  import javax.script.ScriptEngine;
45  import javax.script.ScriptEngineManager;
46  import javax.script.ScriptException;
47  
48  import org.apache.commons.jexl3.JexlException;
49  import org.apache.commons.jexl3.introspection.JexlPermissions;
50  import org.junit.jupiter.api.AfterEach;
51  import org.junit.jupiter.api.BeforeEach;
52  import org.junit.jupiter.api.Test;
53  
54  class JexlScriptEngineTest {
55      public static class Errors {
56          public int illegal() {
57              throw new IllegalArgumentException("jexl");
58          }
59          public int npe() {
60              throw new NullPointerException("jexl");
61          }
62      }
63      private static final List<String> NAMES = Arrays.asList("JEXL", "Jexl", "jexl",
64                                                              "JEXL2", "Jexl2", "jexl2",
65                                                              "JEXL3", "Jexl3", "jexl3");
66      private static final List<String> EXTENSIONS = Arrays.asList("jexl", "jexl2", "jexl3");
67  
68      private static final List<String> MIMES = Arrays.asList("application/x-jexl",
69                                                              "application/x-jexl2",
70                                                              "application/x-jexl3");
71  
72      private static final String LF = System.lineSeparator();
73  
74      /** The logger name used by the LoggingPermissions wrapper in this test. */
75      private static final String PERMS_LOGGER = "jexl.test.permissions";
76  
77      @BeforeEach
78      void setUp() {
79          // quiesce the LoggingPermissions logger, set level to error (instead of info)
80          java.util.logging.Logger.getLogger(PERMS_LOGGER).setLevel(java.util.logging.Level.SEVERE);
81      }
82  
83      @AfterEach
84      void tearDown() {
85          JexlScriptEngine.setInstance(null);
86          JexlScriptEngineFactory.setDefaultPermissions(null);
87      }
88  
89      @Test
90      void testCompile() throws Exception {
91          final ScriptEngineManager manager = new ScriptEngineManager();
92          final JexlScriptEngine engine = (JexlScriptEngine) manager.getEngineByName("JEXL");
93          final ScriptContext ctxt = engine.getContext();
94          final String str = null;
95          final Reader reader = null;
96          assertThrows(NullPointerException.class, () -> engine.compile(str));
97          assertThrows(NullPointerException.class, () -> engine.compile(reader));
98          final CompiledScript script0 = engine.compile(new StringReader("3 + 4"));
99          assertEquals(engine, script0.getEngine());
100         Object result = script0.eval();
101         assertEquals(7, result);
102         result = script0.eval();
103         assertEquals(7, result);
104         result = engine.eval(new StringReader("38 + 4"));
105         assertEquals(42, result);
106         result = engine.eval("38 + 4");
107         assertEquals(42, result);
108         // next test
109         final CompiledScript script1 = engine.compile("3 + 4");
110         assertEquals(engine, script1.getEngine());
111         Object result1 = script1.eval();
112         assertEquals(7, result1);
113         result1 = script1.eval();
114         assertEquals(7, result1);
115         // next test
116         ctxt.setAttribute("x", 20, ScriptContext.ENGINE_SCOPE);
117         ctxt.setAttribute("y", 22, ScriptContext.ENGINE_SCOPE);
118         final CompiledScript script2 = engine.compile("x + y");
119         Object result2 = script2.eval();
120         assertEquals(42, result2);
121         ctxt.setAttribute("x", -20, ScriptContext.ENGINE_SCOPE);
122         ctxt.setAttribute("y", -22, ScriptContext.ENGINE_SCOPE);
123         result2 = script2.eval();
124         assertEquals(-42, result2);
125     }
126 
127     @Test
128     void testDirectNew() throws Exception {
129         final ScriptEngine engine = new JexlScriptEngine();
130         final Integer initialValue = 123;
131         assertEquals(initialValue,engine.eval("123"));
132     }
133 
134     @Test
135     void testDottedNames() throws Exception {
136         final ScriptEngineManager manager = new ScriptEngineManager();
137         assertNotNull(manager, "Manager should not be null");
138         final ScriptEngine engine = manager.getEngineByName("JEXL");
139         assertNotNull(engine, "Engine should not be null (JEXL)");
140         engine.eval("this.is.a.test=null");
141         assertNull(engine.get("this.is.a.test"));
142         assertEquals(Boolean.TRUE, engine.eval("empty(this.is.a.test)"));
143         final Object mymap = engine.eval("testmap={ 'key1' : 'value1', 'key2' : 'value2' }");
144         assertInstanceOf(Map.class, mymap);
145         assertEquals(2,((Map<?, ?>)mymap).size());
146     }
147 
148     @Test
149     void testErrors() throws Exception {
150         final JexlPermissions permissions = new JexlPermissions.ClassPermissions(Errors.class);
151         JexlScriptEngineFactory.setDefaultPermissions(permissions);
152         final ScriptEngineManager manager = new ScriptEngineManager();
153         final JexlScriptEngine engine = (JexlScriptEngine) manager.getEngineByName("JEXL");
154         engine.put("errors", new Errors());
155         assertInstanceOf(NullPointerException.class, assertThrows(ScriptException.class, () -> engine.eval("errors.npe()")).getCause());
156         assertInstanceOf(IllegalArgumentException.class, assertThrows(ScriptException.class, () -> engine.eval("errors.illegal()")).getCause());
157         final CompiledScript script0 = engine.compile("errors.npe()");
158         assertInstanceOf(NullPointerException.class, assertThrows(ScriptException.class, script0::eval).getCause());
159         final CompiledScript script1 = engine.compile("errors.illegal()");
160         assertInstanceOf(IllegalArgumentException.class, assertThrows(ScriptException.class, script1::eval).getCause());
161     }
162 
163     @Test
164     void testNulls() {
165         final ScriptEngineManager manager = new ScriptEngineManager();
166         assertNotNull(manager, "Manager should not be null");
167         final ScriptEngine engine = manager.getEngineByName("jexl3");
168         assertNotNull(engine, "Engine should not be null (name)");
169         assertNotNull(engine.getFactory());
170         assertThrows(NullPointerException.class, () -> engine.eval((String) null));
171         assertThrows(NullPointerException.class, () -> engine.eval((Reader) null));
172         final ScriptContext ctxt = null;
173         assertThrows(NullPointerException.class, () -> engine.eval((String) null, ctxt));
174         assertThrows(NullPointerException.class, () -> engine.eval((Reader) null, ctxt));
175     }
176 
177     @Test
178     void testScopes() throws Exception {
179         final ScriptEngineManager manager = new ScriptEngineManager();
180         assertNotNull(manager, "Manager should not be null");
181         final ScriptEngine engine = manager.getEngineByName("jexl3");
182         assertNotNull(engine, "Engine should not be null (name)");
183         manager.put("global", 1);
184         engine.put("local", 10);
185         manager.put("both", 7);
186         engine.put("both", 7);
187         engine.eval("local=local+1");
188         engine.eval("global=global+1");
189         engine.eval("both=both+1"); // should update engine value only
190         engine.eval("newvar=42;");
191         assertEquals(2,manager.get("global"));
192         assertEquals(11,engine.get("local"));
193         assertEquals(7,manager.get("both"));
194         assertEquals(8,engine.get("both"));
195         assertEquals(42,engine.get("newvar"));
196         assertNull(manager.get("newvar"));
197     }
198 
199     @Test
200     void testScriptEngineFactory() {
201         final JexlScriptEngineFactory factory = new JexlScriptEngineFactory();
202         assertEquals("JEXL Engine", factory.getParameter(ScriptEngine.ENGINE));
203         assertEquals("3.7", factory.getParameter(ScriptEngine.ENGINE_VERSION));
204         assertEquals("JEXL", factory.getParameter(ScriptEngine.LANGUAGE));
205         assertEquals("3.7", factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
206         assertNull(factory.getParameter("THREADING"));
207         assertEquals(NAMES, factory.getParameter(ScriptEngine.NAME));
208         assertEquals(EXTENSIONS, factory.getExtensions());
209         assertEquals(MIMES, factory.getMimeTypes());
210 
211         assertEquals("42;", factory.getProgram("42"));
212         assertEquals("str.substring(3,4)", factory.getMethodCallSyntax("str", "substring", "3", "4"));
213     }
214 
215     @Test
216     void testScripting() throws Exception {
217         final ScriptEngineManager manager = new ScriptEngineManager();
218         assertNotNull(manager, "Manager should not be null");
219         final ScriptEngine engine = manager.getEngineByName("jexl3");
220         final Integer initialValue = 123;
221         assertEquals(initialValue, engine.eval("123"));
222         assertEquals(initialValue, engine.eval("0;123")); // multiple statements
223         final ScriptException xscript = assertThrows(ScriptException.class,
224                 () -> engine.eval("sys=context.class.forName(\"java.lang.System\");now=sys.currentTimeMillis();"));
225         final JexlException.Property xjexl = (JexlException.Property) xscript.getCause();
226         assertEquals("class", xjexl.getProperty());
227         engine.put("value", initialValue);
228         assertEquals(initialValue, engine.get("value"));
229         final Integer newValue = 124;
230         assertEquals(newValue, engine.eval("old=value;value=value+1"));
231         assertEquals(initialValue, engine.get("old"));
232         assertEquals(newValue, engine.get("value"));
233         assertEquals(engine.getContext(), engine.get(JexlScriptEngine.CONTEXT_KEY));
234         // Check behavior of JEXL object
235         assertEquals(engine.getContext().getReader(), engine.eval("JEXL.in"));
236         assertEquals(engine.getContext().getWriter(), engine.eval("JEXL.out"));
237         assertEquals(engine.getContext().getErrorWriter(), engine.eval("JEXL.err"));
238         assertEquals(System.class, engine.eval("JEXL.System"));
239     }
240 
241     @Test
242     void testScriptingGetBy() {
243         final ScriptEngineManager manager = new ScriptEngineManager();
244         assertNotNull(manager, "Manager should not be null");
245         for (final String name : NAMES) {
246             final ScriptEngine engine = manager.getEngineByName(name);
247             assertNotNull(engine, "Engine should not be null (name)");
248         }
249         for (final String extension : EXTENSIONS) {
250             final ScriptEngine engine = manager.getEngineByExtension(extension);
251             assertNotNull(engine, "Engine should not be null (extension)");
252         }
253         for (final String mime : MIMES) {
254             final ScriptEngine engine = manager.getEngineByMimeType(mime);
255             assertNotNull(engine, "Engine should not be null (mime)");
256         }
257     }
258     @Test
259     void testScriptingInstance0() throws Exception {
260         JexlScriptEngineFactory.setDefaultPermissions(JexlPermissions.UNRESTRICTED);
261         final ScriptEngineManager manager = new ScriptEngineManager();
262         final ScriptEngine engine = manager.getEngineByName("jexl3");
263         final Long time2 = (Long) engine.eval(
264                 "sys=context.class.forName(\"java.lang.System\");"
265                         + "now=sys.currentTimeMillis();");
266         assertTrue(time2 <= System.currentTimeMillis());
267     }
268 
269     @Test
270     void testScriptingPermissions1() throws Exception {
271         // shows what is required to access System.currentTimeMillis()
272         JexlScriptEngineFactory.setDefaultPermissions(
273             JexlPermissions.RESTRICTED.compose(
274                 "javax.script { +SimpleScriptContext { getClass(); } }"
275                     + "java.lang { "
276                     + "+System { currentTimeMillis(); }"
277                     + "+Class { forName(); } "
278                     + "}").logging(PERMS_LOGGER));
279         final ScriptEngineManager manager = new ScriptEngineManager();
280         final ScriptEngine engine = manager.getEngineByName("jexl3");
281         final Long time2 = (Long) engine.eval(
282                 "sys=context.class.forName(\"java.lang.System\");"
283                         + "now=sys.currentTimeMillis();");
284         assertTrue(time2 <= System.currentTimeMillis());
285     }
286 
287     @Test
288     void testMain0() throws Exception {
289         final StringWriter strw = new StringWriter();
290         final StringReader strr = new StringReader("a=20\nb=22\na+b\n//q!\n");
291         Main.run(new BufferedReader(strr), new PrintWriter(strw), null);
292         final String ctl = "> >> 20" + LF +
293                 "> >> 22" + LF +
294                 "> >> 42" + LF +
295                 "> ";
296         assertEquals(ctl, strw.toString());
297     }
298 
299     @Test
300     void testMain1() throws Exception {
301         final StringWriter strw = new StringWriter();
302         final StringReader strr = new StringReader("args[0]+args[1]");
303         Main.run(new BufferedReader(strr), new PrintWriter(strw), new Object[]{20, 22});
304         final String ctl = ">>: 42" + LF;
305         assertEquals(ctl, strw.toString());
306     }
307 
308     @Test
309     void testMain2() throws Exception {
310         final PrintStream originalOut = System.out;
311         Path file = null;
312         try {
313             file = Files.createTempFile("test-jsr233", ".jexl");
314             try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile()));
315                  ByteArrayOutputStream outContent = new ByteArrayOutputStream();
316                  PrintStream printStream = new PrintStream(outContent)) {
317                 writer.write("a=20;\nb=22;\na+b\n");
318                 writer.flush();
319                 System.setOut(printStream);
320                 final String ctl = ">>: 42" + LF;
321                 Main.main(new String[]{file.toString()});
322                 assertEquals(ctl, outContent.toString());
323             } finally {
324                 System.setOut(originalOut);
325             }
326         } finally {
327             if (file != null) {
328                 Files.delete(file);
329             }
330         }
331     }
332 }