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 * http://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; 19 20 import java.nio.charset.Charset; 21 import java.util.Arrays; 22 import java.util.Collection; 23 import java.util.Map; 24 import java.util.function.IntFunction; 25 26 import org.apache.commons.jexl3.internal.Engine; 27 import org.apache.commons.jexl3.internal.SoftCache; 28 import org.apache.commons.jexl3.introspection.JexlPermissions; 29 import org.apache.commons.jexl3.introspection.JexlSandbox; 30 import org.apache.commons.jexl3.introspection.JexlUberspect; 31 import org.apache.commons.logging.Log; 32 33 /** 34 * Configures and builds a JexlEngine. 35 * 36 * <p> 37 * The builder allow fine-tuning an engine instance behavior according to various control needs. 38 * Check <em>{@link #JexlBuilder()}</em> for permission impacts starting with <em>JEXL 3.3</em>. 39 * </p><p> 40 * Broad configurations elements are controlled through the features ({@link JexlFeatures}) that can restrict JEXL 41 * syntax - for instance, only expressions with no-side effects - and permissions ({@link JexlPermissions}) that control 42 * the visible set of objects - for instance, avoiding access to any object in java.rmi.* -. 43 * </p><p> 44 * Fine error control and runtime-overridable behaviors are implemented through options ({@link JexlOptions}). Most 45 * common flags accessible from the builder are reflected in its options ({@link #options()}). 46 * </p><p> 47 * The <code>silent</code> flag tells the engine what to do with the error; when true, errors are logged as 48 * warning, when false, they throw {@link JexlException} exceptions. 49 * </p><p> 50 * The <code>strict</code> flag tells the engine when and if null as operand is considered an error. The <code>safe</code> 51 * flog determines if safe-navigation is used. Safe-navigation allows an evaluation shortcut and return null in expressions 52 * that attempts dereferencing null, typically a method call or accessing a property. 53 * </p><p> 54 * The <code>lexical</code> and <code>lexicalShade</code> flags can be used to enforce a lexical scope for 55 * variables and parameters. The <code>lexicalShade</code> can be used to further ensure no global variable can be 56 * used with the same name as a local one even after it goes out of scope. The corresponding feature flags should be 57 * preferred since they will detect violations at parsing time. (see {@link JexlFeatures}) 58 * </p><p> 59 * The following rules apply on silent and strict flags: 60 * </p> 61 * <ul> 62 * <li>When "silent" & "not-strict": 63 * <p> 0 & null should be indicators of "default" values so that even in an case of error, 64 * something meaningful can still be inferred; may be convenient for configurations. 65 * </p> 66 * </li> 67 * <li>When "silent" & "strict": 68 * <p>One should probably consider using null as an error case - ie, every object 69 * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form 70 * can be used to workaround exceptional cases. 71 * Use case could be configuration with no implicit values or defaults. 72 * </p> 73 * </li> 74 * <li>When "not-silent" & "not-strict": 75 * <p>The error control grain is roughly on par with JEXL 1.0</p> 76 * </li> 77 * <li>When "not-silent" & "strict": 78 * <p>The finest error control grain is obtained; it is the closest to Java code - 79 * still augmented by "script" capabilities regarding automated conversions and type matching. 80 * </p> 81 * </li> 82 * </ul> 83 */ 84 public class JexlBuilder { 85 /** 86 * The set of default permissions used when creating a new builder. 87 * <p>Static but modifiable so these default permissions can be changed to a purposeful set.</p> 88 * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p> 89 * <p>In JEXL 3.2, these were equivalent to {@link JexlPermissions#UNRESTRICTED}.</p> 90 */ 91 private static JexlPermissions PERMISSIONS = JexlPermissions.RESTRICTED; 92 93 /** The default maximum expression length to hit the expression cache. */ 94 protected static final int CACHE_THRESHOLD = 64; 95 96 /** 97 * Sets the default permissions. 98 * @param permissions the permissions 99 */ 100 public static void setDefaultPermissions(final JexlPermissions permissions) { 101 PERMISSIONS = permissions == null ? JexlPermissions.RESTRICTED : permissions; 102 } 103 104 /** The JexlUberspect instance. */ 105 private JexlUberspect uberspect; 106 107 /** The {@link JexlUberspect} resolver strategy. */ 108 private JexlUberspect.ResolverStrategy strategy; 109 110 /** The set of permissions. */ 111 private JexlPermissions permissions; 112 113 /** The sandbox. */ 114 private JexlSandbox sandbox; 115 116 /** The Log to which all JexlEngine messages will be logged. */ 117 private Log logger; 118 119 /** Whether error messages will carry debugging information. */ 120 private Boolean debug; 121 122 /** Whether interrupt throws JexlException.Cancel. */ 123 private Boolean cancellable; 124 125 /** The options. */ 126 private final JexlOptions options = new JexlOptions(); 127 128 /** Whether getVariables considers all potential equivalent syntactic forms. */ 129 private int collectMode = 1; 130 131 /** The {@link JexlArithmetic} instance. */ 132 private JexlArithmetic arithmetic; 133 134 /** The cache size. */ 135 private int cache = -1; 136 137 /** The cache class factory. */ 138 private IntFunction<JexlCache<?,?>> cacheFactory = SoftCache::new; 139 140 /** The stack overflow limit. */ 141 private int stackOverflow = Integer.MAX_VALUE; 142 143 /** The maximum expression length to hit the expression cache. */ 144 private int cacheThreshold = CACHE_THRESHOLD; 145 146 /** The charset. */ 147 private Charset charset = Charset.defaultCharset(); 148 149 /** The class loader. */ 150 private ClassLoader loader; 151 152 /** The features. */ 153 private JexlFeatures features; 154 155 /** 156 * Default constructor. 157 * <p> 158 * As of JEXL 3.3, to reduce the security risks inherent to JEXL"s purpose, the builder will use a set of 159 * restricted permissions as a default to create the {@link JexlEngine} instance. This will greatly reduce which classes 160 * and methods are visible to JEXL and usable in scripts using default implicit behaviors. 161 * </p><p> 162 * However, without mitigation, this change will likely break some scripts at runtime, especially those exposing 163 * your own class instances through arguments, contexts or namespaces. 164 * The new default set of allowed packages and denied classes is described by {@link JexlPermissions#RESTRICTED}. 165 * </p><p> 166 * The recommended mitigation if your usage of JEXL is impacted is to first thoroughly review what should be 167 * allowed and exposed to script authors and implement those through a set of {@link JexlPermissions}; 168 * those are easily created using {@link JexlPermissions#parse(String...)}. 169 * </p><p> 170 * In the urgent case of a strict 3.2 compatibility, the simplest and fastest mitigation is to use the 'unrestricted' 171 * set of permissions. The builder must be explicit about it either by setting the default permissions with a 172 * statement like <code>JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED);</code> or with a more precise 173 * one like <code>new JexlBuilder().permissions({@link JexlPermissions#UNRESTRICTED})</code>. 174 * </p><p> 175 * Note that an explicit call to {@link #uberspect(JexlUberspect)} will supersede any permissions related behavior 176 * by using the {@link JexlUberspect} provided as argument used as-is in the created {@link JexlEngine}. 177 * </p> 178 * @since 3.3 179 */ 180 public JexlBuilder() { 181 this.permissions = PERMISSIONS; 182 } 183 184 /** @return whether antish resolution is enabled */ 185 public boolean antish() { 186 return options.isAntish(); 187 } 188 189 /** 190 * Sets whether the engine will resolve antish variable names. 191 * 192 * @param flag true means antish resolution is enabled, false disables it 193 * @return this builder 194 */ 195 public JexlBuilder antish(final boolean flag) { 196 options.setAntish(flag); 197 return this; 198 } 199 200 /** @return the arithmetic */ 201 public JexlArithmetic arithmetic() { 202 return this.arithmetic; 203 } 204 205 /** 206 * Sets the JexlArithmetic instance the engine will use. 207 * 208 * @param a the arithmetic 209 * @return this builder 210 */ 211 public JexlBuilder arithmetic(final JexlArithmetic a) { 212 this.arithmetic = a; 213 options.setStrictArithmetic(a.isStrict()); 214 options.setMathContext(a.getMathContext()); 215 options.setMathScale(a.getMathScale()); 216 return this; 217 } 218 219 /** 220 * @return the cache size 221 */ 222 public int cache() { 223 return cache; 224 } 225 226 /** 227 * Sets the expression cache size the engine will use. 228 * <p>The cache will contain at most <code>size</code> expressions of at most <code>cacheThreshold</code> length. 229 * Note that all JEXL caches are held through SoftReferences and may be garbage-collected.</p> 230 * 231 * @param size if not strictly positive, no cache is used. 232 * @return this builder 233 */ 234 public JexlBuilder cache(final int size) { 235 this.cache = size; 236 return this; 237 } 238 239 /** 240 * @return the cache factory 241 */ 242 public IntFunction<JexlCache<?, ?>> cacheFactory() { 243 return this.cacheFactory; 244 } 245 246 /** 247 * Sets the expression cache size the engine will use. 248 * 249 * @param factory the function to produce a cache. 250 * @return this builder 251 */ 252 public JexlBuilder cacheFactory(final IntFunction<JexlCache<?, ?>> factory) { 253 this.cacheFactory = factory; 254 return this; 255 } 256 257 /** 258 * @return the cache threshold 259 */ 260 public int cacheThreshold() { 261 return cacheThreshold; 262 } 263 264 /** 265 * Sets the maximum length for an expression to be cached. 266 * <p>Expression whose length is greater than this expression cache length threshold will 267 * bypass the cache.</p> 268 * <p>It is expected that a "long" script will be parsed once and its reference kept 269 * around in user-space structures; the jexl expression cache has no added-value in this case.</p> 270 * 271 * @param length if not strictly positive, the value is silently replaced by the default value (64). 272 * @return this builder 273 */ 274 public JexlBuilder cacheThreshold(final int length) { 275 this.cacheThreshold = length > 0? length : CACHE_THRESHOLD; 276 return this; 277 } 278 279 /** 280 * @return the cancellable information flag 281 * @since 3.1 282 */ 283 public Boolean cancellable() { 284 return this.cancellable; 285 } 286 287 /** 288 * Sets the engine behavior upon interruption: throw an JexlException.Cancel or terminates the current evaluation 289 * and return null. 290 * 291 * @param flag true implies the engine throws the exception, false makes the engine return null. 292 * @return this builder 293 * @since 3.1 294 */ 295 public JexlBuilder cancellable(final boolean flag) { 296 this.cancellable = flag; 297 options.setCancellable(flag); 298 return this; 299 } 300 301 /** @return the charset */ 302 public Charset charset() { 303 return charset; 304 } 305 306 /** 307 * Sets the charset to use. 308 * 309 * @param arg the charset 310 * @return this builder 311 * @since 3.1 312 */ 313 public JexlBuilder charset(final Charset arg) { 314 this.charset = arg; 315 return this; 316 } 317 318 /** 319 * @return true if variable collection follows strict syntactic rule 320 * @since 3.2 321 */ 322 public boolean collectAll() { 323 return this.collectMode != 0; 324 } 325 326 /** 327 * Sets whether the engine variable collectors considers all potential forms of variable syntaxes. 328 * 329 * @param flag true means var collections considers constant array accesses equivalent to dotted references 330 * @return this builder 331 * @since 3.2 332 */ 333 public JexlBuilder collectAll(final boolean flag) { 334 return collectMode(flag? 1 : 0); 335 } 336 337 /** 338 * @return 0 if variable collection follows strict syntactic rule 339 * @since 3.2 340 */ 341 public int collectMode() { 342 return this.collectMode; 343 } 344 345 /** 346 * Experimental collector mode setter. 347 * 348 * @param mode 0 or 1 as equivalents to false and true, other values are experimental 349 * @return this builder 350 * @since 3.2 351 */ 352 public JexlBuilder collectMode(final int mode) { 353 this.collectMode = mode; 354 return this; 355 } 356 357 /** 358 * @return a {@link JexlEngine} instance 359 */ 360 public JexlEngine create() { 361 return new Engine(this); 362 } 363 364 /** @return the debugging information flag */ 365 public Boolean debug() { 366 return this.debug; 367 } 368 369 /** 370 * Sets whether the engine will report debugging information when error occurs. 371 * 372 * @param flag true implies debug is on, false implies debug is off. 373 * @return this builder 374 */ 375 public JexlBuilder debug(final boolean flag) { 376 this.debug = flag; 377 return this; 378 } 379 380 /** @return the features */ 381 public JexlFeatures features() { 382 return this.features; 383 } 384 385 /** 386 * Sets the features the engine will use as a base by default. 387 * <p>Note that the script flag will be ignored; the engine will be able to parse expressions and scripts. 388 * <p>Note also that these will apply to template expressions and scripts. 389 * <p>As a last remark, if lexical or lexicalShade are set as features, this 390 * method will also set the corresponding options. 391 * @param f the features 392 * @return this builder 393 */ 394 public JexlBuilder features(final JexlFeatures f) { 395 this.features = f; 396 if (features != null) { 397 if (features.isLexical()) { 398 options.setLexical(true); 399 } 400 if (features.isLexicalShade()) { 401 options.setLexicalShade(true); 402 } 403 } 404 return this; 405 } 406 407 /** 408 * Gets the optional set of imported packages. 409 * @return the set of imports, may be empty, not null 410 */ 411 public Collection<String> imports() { 412 return options.getImports(); 413 } 414 415 /** 416 * Sets the optional set of imports. 417 * @param imports the imported packages 418 * @return this builder 419 */ 420 public JexlBuilder imports(final Collection<String> imports) { 421 options.setImports(imports); 422 return this; 423 } 424 425 /** 426 * Sets the optional set of imports. 427 * @param imports the imported packages 428 * @return this builder 429 */ 430 public JexlBuilder imports(final String... imports) { 431 return imports(Arrays.asList(imports)); 432 } 433 434 /** @return whether lexical scope is enabled */ 435 public boolean lexical() { 436 return options.isLexical(); 437 } 438 439 /** 440 * Sets whether the engine is in lexical mode. 441 * 442 * @param flag true means lexical function scope is in effect, false implies non-lexical scoping 443 * @return this builder 444 * @since 3.2 445 */ 446 public JexlBuilder lexical(final boolean flag) { 447 options.setLexical(flag); 448 return this; 449 } 450 451 /** @return whether lexical shading is enabled */ 452 public boolean lexicalShade() { 453 return options.isLexicalShade(); 454 } 455 456 /** 457 * Sets whether the engine is in lexical shading mode. 458 * 459 * @param flag true means lexical shading is in effect, false implies no lexical shading 460 * @return this builder 461 * @since 3.2 462 */ 463 public JexlBuilder lexicalShade(final boolean flag) { 464 options.setLexicalShade(flag); 465 return this; 466 } 467 468 /** @return the class loader */ 469 public ClassLoader loader() { 470 return loader; 471 } 472 473 /** 474 * Sets the charset to use. 475 * 476 * @param arg the charset 477 * @return this builder 478 * @deprecated since 3.1 use {@link #charset(Charset)} instead 479 */ 480 @Deprecated 481 public JexlBuilder loader(final Charset arg) { 482 return charset(arg); 483 } 484 485 /** 486 * Sets the class loader to use. 487 * 488 * @param l the class loader 489 * @return this builder 490 */ 491 public JexlBuilder loader(final ClassLoader l) { 492 this.loader = l; 493 return this; 494 } 495 496 /** @return the logger */ 497 public Log logger() { 498 return this.logger; 499 } 500 501 /** 502 * Sets the o.a.c.Log instance to use. 503 * 504 * @param log the logger 505 * @return this builder 506 */ 507 public JexlBuilder logger(final Log log) { 508 this.logger = log; 509 return this; 510 } 511 512 /** 513 * @return the map of namespaces. 514 */ 515 public Map<String, Object> namespaces() { 516 return options.getNamespaces(); 517 } 518 519 /** 520 * Sets the default namespaces map the engine will use. 521 * <p> 522 * Each entry key is used as a prefix, each entry value used as a bean implementing 523 * methods; an expression like 'nsx:method(123)' will thus be solved by looking at 524 * a registered bean named 'nsx' that implements method 'method' in that map. 525 * If all methods are static, you may use the bean class instead of an instance as value. 526 * </p> 527 * <p> 528 * If the entry value is a class that has one constructor taking a JexlContext as argument, an instance 529 * of the namespace will be created at evaluation time. It might be a good idea to derive a JexlContext 530 * to carry the information used by the namespace to avoid variable space pollution and strongly type 531 * the constructor with this specialized JexlContext. 532 * </p> 533 * <p> 534 * The key or prefix allows to retrieve the bean that plays the role of the namespace. 535 * If the prefix is null, the namespace is the top-level namespace allowing to define 536 * top-level user-defined namespaces ( ie: myfunc(...) ) 537 * </p> 538 * <p>Note that the JexlContext is also used to try to solve top-level namespaces. This allows ObjectContext 539 * derived instances to call methods on the wrapped object.</p> 540 * 541 * @param ns the map of namespaces 542 * @return this builder 543 */ 544 public JexlBuilder namespaces(final Map<String, Object> ns) { 545 options.setNamespaces(ns); 546 return this; 547 } 548 549 /** @return the current set of options */ 550 public JexlOptions options() { 551 return options; 552 } 553 554 /** @return the permissions */ 555 public JexlPermissions permissions() { 556 return this.permissions; 557 } 558 559 /** 560 * Sets the JexlPermissions instance the engine will use. 561 * 562 * @param p the permissions 563 * @return this builder 564 */ 565 public JexlBuilder permissions(final JexlPermissions p) { 566 this.permissions = p; 567 return this; 568 } 569 570 /** @return true if safe, false otherwise */ 571 public Boolean safe() { 572 return options.isSafe(); 573 } 574 575 /** 576 * Sets whether the engine considers dereferencing null in navigation expressions 577 * as null or triggers an error. 578 * <p><code>x.y()</code> if x is null throws an exception when not safe, 579 * return null and warns if it is.</p> 580 * <p>It is recommended to use <em>safe(false)</em> as an explicit default.</p> 581 * 582 * @param flag true means safe navigation, false throws exception when dereferencing null 583 * @return this builder 584 */ 585 public JexlBuilder safe(final boolean flag) { 586 options.setSafe(flag); 587 return this; 588 } 589 590 /** @return the sandbox */ 591 public JexlSandbox sandbox() { 592 return this.sandbox; 593 } 594 595 /** 596 * Sets the sandbox the engine will use. 597 * 598 * @param box the sandbox 599 * @return this builder 600 */ 601 public JexlBuilder sandbox(final JexlSandbox box) { 602 this.sandbox = box; 603 return this; 604 } 605 606 /** @return the silent error handling flag */ 607 public Boolean silent() { 608 return options.isSilent(); 609 } 610 611 /** 612 * Sets whether the engine will throw JexlException during evaluation when an error is triggered. 613 * <p>When <em>not</em> silent, the engine throws an exception when the evaluation triggers an exception or an 614 * error.</p> 615 * <p>It is recommended to use <em>silent(true)</em> as an explicit default.</p> 616 * @param flag true means no JexlException will occur, false allows them 617 * @return this builder 618 */ 619 public JexlBuilder silent(final boolean flag) { 620 options.setSilent(flag); 621 return this; 622 } 623 624 /** 625 * @return the cache size 626 */ 627 public int stackOverflow() { 628 return stackOverflow; 629 } 630 631 /** 632 * Sets the number of script/expression evaluations that can be stacked. 633 * @param size if not strictly positive, limit is reached when Java StackOverflow is thrown. 634 * @return this builder 635 */ 636 public JexlBuilder stackOverflow(final int size) { 637 this.stackOverflow = size; 638 return this; 639 } 640 641 /** @return the JexlUberspect strategy */ 642 public JexlUberspect.ResolverStrategy strategy() { 643 return this.strategy; 644 } 645 646 /** 647 * Sets the JexlUberspect strategy the engine will use. 648 * <p>This is ignored if the uberspect has been set. 649 * 650 * @param rs the strategy 651 * @return this builder 652 */ 653 public JexlBuilder strategy(final JexlUberspect.ResolverStrategy rs) { 654 this.strategy = rs; 655 return this; 656 } 657 658 /** @return true if strict, false otherwise */ 659 public Boolean strict() { 660 return options.isStrict(); 661 } 662 663 /** 664 * Sets whether the engine considers unknown variables, methods, functions and constructors as errors or 665 * evaluates them as null. 666 * <p>When <em>not</em> strict, operators or functions using null operands return null on evaluation. When 667 * strict, those raise exceptions.</p> 668 * <p>It is recommended to use <em>strict(true)</em> as an explicit default.</p> 669 * 670 * @param flag true means strict error reporting, false allows them to be evaluated as null 671 * @return this builder 672 */ 673 public JexlBuilder strict(final boolean flag) { 674 options.setStrict(flag); 675 return this; 676 } 677 678 /** @return the uberspect */ 679 public JexlUberspect uberspect() { 680 return this.uberspect; 681 } 682 683 /** 684 * Sets the JexlUberspect instance the engine will use. 685 * 686 * @param u the uberspect 687 * @return this builder 688 */ 689 public JexlBuilder uberspect(final JexlUberspect u) { 690 this.uberspect = u; 691 return this; 692 } 693 }