1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.InputStream;
21 import java.math.MathContext;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.function.Predicate;
26
27 import org.apache.commons.jexl3.introspection.JexlUberspect;
28 import org.junit.jupiter.api.Assertions;
29 import org.junit.jupiter.api.Test;
30
31
32
33
34 public class JexlConfigLoaderTest {
35
36
37 public static class MathNs {
38 public static int square(final int n) { return n * n; }
39 public static double abs(final double x) { return Math.abs(x); }
40 }
41
42
43 public static class Permitted {
44 public String greet(final String name) { return "Hello, " + name; }
45 }
46
47 private static InputStream yaml(final String content) {
48 return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
49 }
50
51 private InputStream resource(final String name) {
52 final InputStream in = getClass().getResourceAsStream(name);
53 Assertions.assertNotNull(in, "test resource not found: " + name);
54 return in;
55 }
56
57 @Test
58 void testLoadFromResource() throws Exception {
59 try (InputStream in = resource("test-config.yaml")) {
60 final JexlBuilder builder = JexlConfigLoader.load(in);
61 Assertions.assertNotNull(builder);
62 }
63 }
64
65 @Test
66 void testStrictModeFromYaml() throws Exception {
67 try (InputStream in = yaml("strict: true\nsafe: false\n")) {
68 final JexlEngine engine = JexlConfigLoader.load(in).create();
69 Assertions.assertThrows(JexlException.class,
70 () -> engine.createExpression("undeclaredVar + 1").evaluate(new MapContext()));
71 }
72 }
73
74 @Test
75 void testFeaturesFromYaml() throws Exception {
76 try (InputStream in = yaml("features:\n loops: true\n lambda: true\n sideEffect: true\n")) {
77 final JexlEngine engine = JexlConfigLoader.load(in).create();
78 final JexlScript loop = engine.createScript(
79 "var s = 0; for (var i : [1,2,3]) { s += i; } s");
80 Assertions.assertEquals(6, ((Number) loop.execute(new MapContext())).intValue());
81 }
82 }
83
84 @Test
85 void testNamespaceFromYaml() throws Exception {
86 final String ns = MathNs.class.getName();
87 try (InputStream in = yaml("permissions:\n base: RESTRICTED\nnamespaces:\n mymath: " + ns + "\n")) {
88 final JexlEngine engine = JexlConfigLoader.load(in).create();
89 final Object result = engine.createScript("mymath:square(4)")
90 .execute(new MapContext());
91 Assertions.assertEquals(16, result);
92 }
93 }
94
95 @Test
96 void testPermissionsFromYaml() throws Exception {
97 try (InputStream in = resource("test-config.yaml")) {
98 final JexlEngine engine = JexlConfigLoader.load(in).create();
99 Assertions.assertThrows(JexlException.class,
100 () -> engine.createScript("new('java.lang.Runtime')").execute(new MapContext()));
101 }
102 }
103
104 @Test
105 void testFullResourceConfig() throws Exception {
106 try (InputStream in = resource("test-config.yaml")) {
107 final JexlEngine engine = JexlConfigLoader.load(in).create();
108 Assertions.assertNotNull(engine);
109
110 Assertions.assertThrows(JexlException.class,
111 () -> engine.createExpression("ghost + 1").evaluate(new MapContext()));
112
113 final Object r = engine.createScript("math:random() >= 0").execute(new MapContext());
114 Assertions.assertEquals(Boolean.TRUE, r);
115
116 final Number sum = (Number) engine.createScript(
117 "var s = 0; for (var i : [1,2,3]) { s += i; } s").execute(new MapContext());
118 Assertions.assertEquals(6, sum.intValue());
119 }
120 }
121
122 @Test
123 void testEngineConvenience() throws Exception {
124 try (InputStream in = yaml("strict: false\n")) {
125 final JexlEngine engine = JexlConfigLoader.engine(in);
126 Assertions.assertNotNull(engine);
127 }
128 }
129
130 @Test
131 void testInvalidArithmeticClass() throws Exception {
132 try (InputStream in = yaml("arithmetic:\n clazz: com.example.NoSuchClass\n strict: true\n")) {
133 Assertions.assertThrows(IllegalArgumentException.class,
134 () -> JexlConfigLoader.load(in));
135 }
136 }
137
138 @Test
139 void testInvalidNamespaceClass() throws Exception {
140 try (InputStream in = yaml("namespaces:\n bad: com.example.NoSuchClass\n")) {
141 Assertions.assertThrows(IllegalArgumentException.class,
142 () -> JexlConfigLoader.load(in));
143 }
144 }
145
146 @Test
147 void testEmptyYaml() throws Exception {
148 try (InputStream in = yaml("")) {
149 Assertions.assertNotNull(JexlConfigLoader.load(in).create());
150 }
151 }
152
153 @Test
154 void testCommentOnlyYaml() throws Exception {
155 try (InputStream in = yaml("# just a comment\n")) {
156 Assertions.assertNotNull(JexlConfigLoader.load(in).create());
157 }
158 }
159
160 @Test
161 void testInlineComment() throws Exception {
162 try (InputStream in = yaml("strict: true # enable strict mode\ncache: 256 # cache size\n")) {
163 Assertions.assertNotNull(JexlConfigLoader.load(in).create());
164 }
165 }
166
167 @Test
168 void testImportsList() throws Exception {
169 try (InputStream in = yaml("imports:\n - java.lang\n - java.util\n")) {
170 Assertions.assertNotNull(JexlConfigLoader.load(in).create());
171 }
172 }
173
174 @Test
175 void testPermissionsWithRules() throws Exception {
176 final String canonical = Permitted.class.getCanonicalName();
177 final String binary = Permitted.class.getName();
178 final String content =
179 "permissions:\n"
180 + " base: RESTRICTED\n"
181 + " classes:\n"
182 + " - " + canonical + "\n";
183 try (InputStream in = yaml(content)) {
184 final JexlEngine engine = JexlConfigLoader.load(in).create();
185 final Object result = engine.createScript(
186 "new('" + binary + "').greet('World')").execute(new MapContext());
187 Assertions.assertEquals("Hello, World", result);
188 }
189 }
190
191 @Test
192 void testUnrestrictedBase() throws Exception {
193 try (InputStream in = yaml("permissions:\n base: UNRESTRICTED\n")) {
194 Assertions.assertNotNull(JexlConfigLoader.load(in).create());
195 }
196 }
197
198 @Test
199 void testSecureBase() throws Exception {
200 try (InputStream in = yaml("permissions:\n base: SECURE\n")) {
201 final JexlBuilder builder = JexlConfigLoader.load(in);
202 Assertions.assertSame(org.apache.commons.jexl3.introspection.JexlPermissions.SECURE,
203 builder.permissions());
204 }
205
206 try (InputStream in = yaml("permissions:\n rules:\n")) {
207 final JexlBuilder builder = JexlConfigLoader.load(in);
208 Assertions.assertSame(org.apache.commons.jexl3.introspection.JexlPermissions.NONE,
209 builder.permissions());
210 }
211 }
212
213 @Test
214 void testNoneBaseFromYaml() throws Exception {
215 try (InputStream in = yaml("permissions:\n base: NONE\n rules:\n - \"java.lang { +String{} }\"\n")) {
216 final JexlBuilder builder = JexlConfigLoader.load(in);
217 final org.apache.commons.jexl3.introspection.JexlPermissions perms = builder.permissions();
218 Assertions.assertTrue(perms.allow(String.class));
219 Assertions.assertFalse(perms.allow(Integer.class));
220 Assertions.assertFalse(perms.allow(System.class));
221 }
222 }
223
224 @Test
225 void testMigrationYaml() throws Exception {
226
227 try (InputStream in = resource("jexl.yaml")) {
228 final JexlEngine engine = JexlConfigLoader.load(in).create();
229
230 final Number sum = (Number) engine.createScript(
231 "var s = 0; for (var i : [1,2,3]) { s += i; } s").execute(new MapContext());
232 Assertions.assertEquals(6, sum.intValue());
233
234 final Object list = engine.createScript(
235 "new('java.util.ArrayList')").execute(new MapContext());
236 Assertions.assertNotNull(list);
237 }
238 }
239
240 @Test
241 void testLoggingFromYaml() throws Exception {
242
243 try (InputStream in = yaml("permissions:\n base: RESTRICTED\n logging: jexl.config.permissions\n")) {
244 final JexlBuilder builder = JexlConfigLoader.load(in);
245 Assertions.assertTrue(builder.permissions()
246 instanceof org.apache.commons.jexl3.introspection.JexlPermissions.LoggingPermissions);
247 }
248
249 try (InputStream in = yaml("permissions:\n base: RESTRICTED\n logging:\n")) {
250 Assertions.assertTrue(JexlConfigLoader.load(in).permissions()
251 instanceof org.apache.commons.jexl3.introspection.JexlPermissions.LoggingPermissions);
252 }
253
254 try (InputStream in = yaml("permissions:\n base: RESTRICTED\n")) {
255 Assertions.assertFalse(JexlConfigLoader.load(in).permissions()
256 instanceof org.apache.commons.jexl3.introspection.JexlPermissions.LoggingPermissions);
257 }
258 }
259
260 @Test
261 void testOptionFlagsFromYaml() throws Exception {
262 final String content =
263 "silent: true\n"
264 + "safe: false\n"
265 + "cancellable: false\n"
266 + "antish: false\n"
267 + "lexical: true\n"
268 + "lexicalShade: true\n"
269 + "strictInterpolation: true\n"
270 + "booleanLogical: true\n";
271 try (InputStream in = yaml(content)) {
272 final JexlBuilder builder = JexlConfigLoader.load(in);
273 final JexlOptions o = builder.options();
274 Assertions.assertTrue(o.isSilent());
275 Assertions.assertFalse(o.isSafe());
276 Assertions.assertFalse(o.isCancellable());
277 Assertions.assertFalse(o.isAntish());
278 Assertions.assertFalse(builder.antish());
279 Assertions.assertTrue(o.isLexical());
280 Assertions.assertTrue(builder.lexical());
281 Assertions.assertTrue(o.isLexicalShade());
282 Assertions.assertTrue(o.isStrictInterpolation());
283 Assertions.assertTrue(o.isBooleanLogical());
284 }
285 }
286
287 @Test
288 void testNumericAndMiscOptionsFromYaml() throws Exception {
289 final String content =
290 "cache: 256\n"
291 + "cacheThreshold: 32\n"
292 + "stackOverflow: 1024\n"
293 + "collectMode: 1\n"
294 + "debug: true\n"
295 + "charset: UTF-16\n"
296 + "strategy: MAP_STRATEGY\n";
297 try (InputStream in = yaml(content)) {
298 final JexlBuilder builder = JexlConfigLoader.load(in);
299 Assertions.assertEquals(256, builder.cache());
300 Assertions.assertEquals(java.nio.charset.StandardCharsets.UTF_16, builder.charset());
301 Assertions.assertSame(JexlUberspect.MAP_STRATEGY, builder.strategy());
302 Assertions.assertNotNull(builder.create());
303 }
304 }
305
306 @Test
307 void testParseBoolVariants() throws Exception {
308
309 try (InputStream in = yaml("strict: yes\nsafe: on\ncancellable: off\n")) {
310 final JexlOptions o = JexlConfigLoader.load(in).options();
311 Assertions.assertTrue(o.isStrict());
312 Assertions.assertTrue(o.isSafe());
313 Assertions.assertFalse(o.isCancellable());
314 }
315 }
316
317 @Test
318 void testArithmeticMathContextFromYaml() throws Exception {
319
320 try (InputStream in = yaml("arithmetic:\n strict: false\n mathContext: DECIMAL64\n mathScale: 5\n")) {
321 final JexlArithmetic a = JexlConfigLoader.load(in).arithmetic();
322 Assertions.assertNotNull(a);
323 Assertions.assertEquals(MathContext.DECIMAL64, a.getMathContext());
324 Assertions.assertEquals(5, a.getMathScale());
325 Assertions.assertFalse(a.isStrict());
326 }
327
328 try (InputStream in = yaml("arithmetic:\n strict: true\n")) {
329 final JexlArithmetic a = JexlConfigLoader.load(in).arithmetic();
330 Assertions.assertNotNull(a);
331 Assertions.assertTrue(a.isStrict());
332 }
333 }
334
335 @Test
336 void testStrategyJexl() throws Exception {
337
338 try (InputStream in = yaml("strategy: JEXL_STRATEGY\n")) {
339 Assertions.assertSame(JexlUberspect.JEXL_STRATEGY, JexlConfigLoader.load(in).strategy());
340 }
341
342 try (InputStream in = yaml("cache: 16\n")) {
343 Assertions.assertNull(JexlConfigLoader.load(in).strategy());
344 }
345 }
346
347 @Test
348 void testPragmasFromYaml() throws Exception {
349 final String config = "pragmas:\n - dlm.* # any dlm pragma\n - jexl.import\n";
350 try (InputStream in = yaml(config)) {
351 final JexlBuilder builder = JexlConfigLoader.load(in);
352 final Predicate<String> test = builder.features().pragmaTest();
353 Assertions.assertNotSame(JexlFeatures.TEST_STR_TRUE, test);
354 Assertions.assertTrue(test.test("dlm.foo"));
355 Assertions.assertTrue(test.test("dlm.foo.bar"));
356 Assertions.assertTrue(test.test("jexl.import"));
357 Assertions.assertFalse(test.test("dlm"));
358 Assertions.assertFalse(test.test("jexl.importer"));
359 Assertions.assertFalse(test.test("jexl.options"));
360 final JexlEngine engine = builder.create();
361 final JexlScript script = engine.createScript("#pragma dlm.foo 42\n#pragma jexl.import 'java.util'\n1");
362 Assertions.assertEquals(42, script.getPragmas().get("dlm.foo"));
363 final JexlException.Feature xfeature = Assertions.assertThrows(JexlException.Feature.class,
364 () -> engine.createScript("#pragma dlm.foo 42\n#pragma jexl.options '+strict'\n1"));
365
366 Assertions.assertEquals(2, xfeature.getInfo().getLine());
367 Assertions.assertTrue(xfeature.getMessage().contains("pragma 'jexl.options' not allowed"),
368 xfeature.getMessage());
369 Assertions.assertThrows(JexlException.Feature.class,
370 () -> engine.createExpression("#pragma other.foo 42\n1"));
371 }
372 }
373
374 @Test
375 void testInlinePragmaFromYaml() throws Exception {
376 try (InputStream in = yaml("pragmas: dlm.* # a single pattern\nstrict: true\n")) {
377 final Predicate<String> test = JexlConfigLoader.load(in).features().pragmaTest();
378 Assertions.assertTrue(test.test("dlm.foo"));
379 Assertions.assertFalse(test.test("jexl.options"));
380 }
381 try (InputStream in = yaml("pragmas: 'jexl.import'\n")) {
382 final Predicate<String> test = JexlConfigLoader.load(in).features().pragmaTest();
383 Assertions.assertTrue(test.test("jexl.import"));
384 Assertions.assertFalse(test.test("dlm.foo"));
385 }
386 }
387
388 @Test
389 void testInvalidPragmasFromYaml() throws Exception {
390 final String[] configs = {
391 "pragmas:\n dlm: true\n",
392 "pragmas:\n - dlm.*\n rules:\n - jexl.import\n",
393 "pragmas: [dlm.*, jexl.import]\n",
394 "pragmas: dlm.*, jexl.import\n",
395 "pragmas:\n - dlm.*, jexl.import\n"
396 };
397 for (final String config : configs) {
398 try (InputStream in = yaml(config)) {
399 Assertions.assertThrows(IllegalArgumentException.class, () -> JexlConfigLoader.load(in), config);
400 }
401 }
402 }
403
404 @Test
405 void testEmptyPragmaItemFromYaml() throws Exception {
406
407 try (InputStream in = yaml("pragmas:\n - \n")) {
408 Assertions.assertSame(JexlFeatures.TEST_STR_FALSE, JexlConfigLoader.load(in).features().pragmaTest());
409 }
410
411 try (InputStream in = yaml("pragmas:\n - dlm.*\n - \n")) {
412 final Predicate<String> test = JexlConfigLoader.load(in).features().pragmaTest();
413 Assertions.assertTrue(test.test("dlm.foo"));
414 Assertions.assertFalse(test.test(""));
415 }
416 }
417
418 @Test
419 void testPragmasAnywhereFromYaml() throws Exception {
420 final String config = "features:\n pragmaAnywhere: true\npragmas:\n - dlm.*\n";
421 try (InputStream in = yaml(config)) {
422 final JexlEngine engine = JexlConfigLoader.load(in).create();
423 final JexlScript script = engine.createScript("var x = 1;\n#pragma dlm.foo 42\nx");
424 Assertions.assertEquals(42, script.getPragmas().get("dlm.foo"));
425 final JexlException.Feature xfeature = Assertions.assertThrows(JexlException.Feature.class,
426 () -> engine.createScript("var x = 1;\n#pragma dlm.foo 42\nx;\n#pragma other.foo 43\nx"));
427 Assertions.assertEquals(4, xfeature.getInfo().getLine());
428 Assertions.assertTrue(xfeature.getMessage().contains("pragma 'other.foo' not allowed"),
429 xfeature.getMessage());
430 }
431 }
432
433 @Test
434 void testStarPragmaFromYamlAllowsAll() throws Exception {
435 try (InputStream in = yaml("pragmas:\n - '*'\n")) {
436 Assertions.assertSame(JexlFeatures.TEST_STR_TRUE, JexlConfigLoader.load(in).features().pragmaTest());
437 }
438 }
439
440 @Test
441 void testEmptyPragmasFromYamlAllowsNone() throws Exception {
442 try (InputStream in = yaml("pragmas:\nstrict: true\n")) {
443 final JexlEngine engine = JexlConfigLoader.load(in).create();
444 Assertions.assertThrows(JexlException.Feature.class,
445 () -> engine.createScript("#pragma dlm.foo 42\n1"));
446 Assertions.assertEquals(1, engine.createScript("1").execute(null));
447 }
448 }
449
450 @Test
451 void testNoPragmasFromYamlAllowsAll() throws Exception {
452 try (InputStream in = yaml("strict: true\n")) {
453 final JexlBuilder builder = JexlConfigLoader.load(in);
454 Assertions.assertSame(JexlFeatures.TEST_STR_TRUE, builder.features().pragmaTest());
455 final JexlScript script = builder.create().createScript("#pragma any.thing 42\n1");
456 Assertions.assertEquals(42, script.getPragmas().get("any.thing"));
457 }
458 }
459
460 @Test
461 void testPragmasFromYamlEquality() throws Exception {
462 final String config = "pragmas:\n - dlm.*\n - jexl.import\n";
463 final JexlFeatures f0;
464 final JexlFeatures f1;
465 try (InputStream in = yaml(config)) {
466 f0 = JexlConfigLoader.load(in).features();
467 }
468 try (InputStream in = yaml(config)) {
469 f1 = JexlConfigLoader.load(in).features();
470 }
471
472 Assertions.assertNotSame(f0.pragmaTest(), f1.pragmaTest());
473 Assertions.assertEquals(f0, f1);
474 Assertions.assertEquals(f0.hashCode(), f1.hashCode());
475
476 final JexlFeatures f2 = new JexlFeatures(f0)
477 .pragmaTest(JexlFeatures.pragmaPatterns(Arrays.asList(" jexl.import", "dlm.* ")));
478 Assertions.assertEquals(f0, f2);
479 final JexlFeatures f3 = new JexlFeatures(f0)
480 .pragmaTest(JexlFeatures.pragmaPatterns(Collections.singletonList("dlm.*")));
481 Assertions.assertNotEquals(f0, f3);
482 }
483
484 @Test
485 void testPragmaPatterns() {
486 Assertions.assertSame(JexlFeatures.TEST_STR_FALSE, JexlFeatures.pragmaPatterns(null));
487 Assertions.assertSame(JexlFeatures.TEST_STR_FALSE, JexlFeatures.pragmaPatterns(Collections.emptyList()));
488 Assertions.assertSame(JexlFeatures.TEST_STR_FALSE, JexlFeatures.pragmaPatterns(Arrays.asList("", " ", null)));
489 Assertions.assertSame(JexlFeatures.TEST_STR_TRUE, JexlFeatures.pragmaPatterns(Arrays.asList("dlm.*", "*")));
490 final Predicate<String> test = JexlFeatures.pragmaPatterns(Arrays.asList("dlm.*", "jexl.import"));
491 Assertions.assertTrue(test.test("dlm.foo"));
492 Assertions.assertTrue(test.test("jexl.import"));
493 Assertions.assertFalse(test.test("jexl.importer"));
494 Assertions.assertFalse(test.test(null));
495 Assertions.assertEquals("jexl.import, dlm.*", test.toString());
496 }
497
498 @Test
499 void testFeaturesPragmaTest() {
500 final JexlFeatures features = new JexlFeatures().pragmaTest(k -> k.startsWith("dlm."));
501
502 Assertions.assertEquals(features, new JexlFeatures(features));
503 final JexlEngine engine = new JexlBuilder().features(features).create();
504 Assertions.assertNotNull(engine.createScript("#pragma dlm.good 1\n1"));
505 Assertions.assertThrows(JexlException.Feature.class, () -> engine.createScript("#pragma other.good 1\n1"));
506
507 Assertions.assertNotNull(engine.createScript(new JexlFeatures(), null, "#pragma other.good 1\n1"));
508 }
509 }