ecma-evaluator 2.0.10 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Badge](https://img.shields.io/badge/link-996.icu-%23FF4D5B.svg?style=flat-square)](https://996.icu/#/en_US)
4
4
  [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg?style=flat-square)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
5
- ![Node](https://img.shields.io/badge/node-%3E=14-blue.svg?style=flat-square)
5
+ ![Node](https://img.shields.io/badge/node-%3E=18-blue.svg?style=flat-square)
6
6
  [![npm version](https://badge.fury.io/js/ecma-evaluator.svg)](https://badge.fury.io/js/ecma-evaluator)
7
7
 
8
8
  A tiny, fast, and **secure** JavaScript expression evaluator for safely evaluating expressions and template strings in a sandboxed environment.
@@ -34,6 +34,6 @@ evalTemplate("Hello {{ name }}!", { name: "World" }); // "Hello World!"
34
34
 
35
35
  - [Getting started](/guide/getting-started)
36
36
  - [API, supported features, security, and usage reference](/guide/reference)
37
- - [Online playground](/#playground)
37
+ - [Online playground](https://axetroy.github.io/ecma-evaluator/#playground)
38
38
 
39
39
  See the [documentation site](https://axetroy.github.io/ecma-evaluator/).
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Badge](https://img.shields.io/badge/link-996.icu-%23FF4D5B.svg?style=flat-square)](https://996.icu/#/zh_CN)
4
4
  [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg?style=flat-square)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
5
- ![Node](https://img.shields.io/badge/node-%3E=14-blue.svg?style=flat-square)
5
+ ![Node](https://img.shields.io/badge/node-%3E=18-blue.svg?style=flat-square)
6
6
  [![npm version](https://badge.fury.io/js/ecma-evaluator.svg)](https://badge.fury.io/js/ecma-evaluator)
7
7
 
8
8
  一个小巧、快速且**安全**的 JavaScript 表达式求值器,用于在沙箱环境中安全地执行表达式和模板字符串。
@@ -34,6 +34,6 @@ evalTemplate("你好,{{ name }}!", { name: "世界" }); // "你好,世界
34
34
 
35
35
  - [快速开始](/zh-CN/guide/getting-started)
36
36
  - [API、支持的特性、安全性和使用说明](/zh-CN/guide/reference)
37
- - [在线体验](/zh-CN/#playground)
37
+ - [在线体验](https://axetroy.github.io/ecma-evaluator/zh-CN/#playground)
38
38
 
39
39
  也可以访问[文档网站](https://axetroy.github.io/ecma-evaluator/zh-CN/)。
@@ -11,6 +11,12 @@ export interface EvaluatorOptions {
11
11
  * @default 10000
12
12
  */
13
13
  maxEvaluationSteps?: number;
14
+ /**
15
+ * Maximum interpreter recursion depth (hard maximum: 2000). Bounds nested
16
+ * expressions so a deeply nested input cannot exhaust the host call stack.
17
+ * @default 1000
18
+ */
19
+ maxDepth?: number;
14
20
  }
15
21
 
16
22
  export declare class Evaluator {
@@ -21,4 +27,8 @@ export declare class Evaluator {
21
27
  evaluate<T = unknown>(expression: string): T;
22
28
  }
23
29
 
24
- export declare function getNodeString(node: Node): string;
30
+ /**
31
+ * Renders an AST node back to a short source-like string. Returns `null` for
32
+ * node types that have no textual representation.
33
+ */
34
+ export declare function getNodeString(node: Node): string | null;
@@ -76,6 +76,7 @@ var mutableMethods = [
76
76
  "Array.prototype.sort",
77
77
  "Array.prototype.fill",
78
78
  "Array.prototype.copyWithin",
79
+ "Object.create",
79
80
  "Object.defineProperty",
80
81
  "Object.defineProperties",
81
82
  "Object.preventExtensions",
@@ -379,6 +380,18 @@ var MAX_STRING_LENGTH = 100000;
379
380
  var MAX_DATA_NODES = 100000;
380
381
  var MAX_BIGINT_EXPONENT = 256n;
381
382
  var MAX_BIGINT_BITS = 65536;
383
+ var DEFAULT_MAX_DEPTH = 1000;
384
+ var MAX_CONFIGURABLE_DEPTH = 2000;
385
+ var MAX_REGEXP_SOURCE_LENGTH = 1000;
386
+ var MAX_REGEXP_FLAGS_LENGTH = 32;
387
+ var NON_SANDBOX_GLOBALS = new Set([
388
+ "WeakRef",
389
+ "FinalizationRegistry",
390
+ "Iterator",
391
+ "SharedArrayBuffer",
392
+ "escape",
393
+ "unescape"
394
+ ]);
382
395
  var TYPED_ARRAY_CONSTRUCTORS = new Set([
383
396
  Int8Array,
384
397
  Uint8Array,
@@ -387,6 +400,7 @@ var TYPED_ARRAY_CONSTRUCTORS = new Set([
387
400
  Uint16Array,
388
401
  Int32Array,
389
402
  Uint32Array,
403
+ "function" == typeof Float16Array ? Float16Array : null,
390
404
  Float32Array,
391
405
  Float64Array,
392
406
  "function" == typeof BigInt64Array ? BigInt64Array : null,
@@ -394,6 +408,39 @@ var TYPED_ARRAY_CONSTRUCTORS = new Set([
394
408
  ].filter(function(constructor) {
395
409
  return "function" == typeof constructor;
396
410
  }));
411
+ function isTypedArrayConstructor(value) {
412
+ return TYPED_ARRAY_CONSTRUCTORS.has(value) || "function" == typeof value && "number" == typeof value.BYTES_PER_ELEMENT;
413
+ }
414
+ var TYPED_ARRAY_MUTATORS = function() {
415
+ var map = new Map();
416
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = void 0;
417
+ try {
418
+ for(var _iterator = TYPED_ARRAY_CONSTRUCTORS[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
419
+ var constructor = _step.value;
420
+ for(var _i = 0, _iter = [
421
+ "set",
422
+ "fill",
423
+ "reverse",
424
+ "sort",
425
+ "copyWithin"
426
+ ]; _i < _iter.length; _i++){
427
+ var name = _iter[_i];
428
+ var method = constructor.prototype[name];
429
+ if ("function" == typeof method && !map.has(method)) map.set(method, name);
430
+ }
431
+ }
432
+ } catch (err) {
433
+ _didIteratorError = true;
434
+ _iteratorError = err;
435
+ } finally{
436
+ try {
437
+ if (!_iteratorNormalCompletion && null != _iterator.return) _iterator.return();
438
+ } finally{
439
+ if (_didIteratorError) throw _iteratorError;
440
+ }
441
+ }
442
+ return map;
443
+ }();
397
444
  var NO_OBJECT = Symbol("no-object");
398
445
  var renderErrorMessage = function(template) {
399
446
  var context = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
@@ -558,14 +605,20 @@ var BINARY_OPERATION_MAP = {
558
605
  function createGlobalScope() {
559
606
  var scope = Object.create(null);
560
607
  var builtin = external_globals_namespaceObject.builtin;
561
- for(var key in builtin)if (!blockedGlobalBuiltIns.includes(key)) {
562
- var isWritable = builtin[key];
563
- Object.defineProperty(scope, key, {
564
- value: globalThis[key],
565
- writable: isWritable,
566
- enumerable: false,
567
- configurable: false
568
- });
608
+ for(var key in builtin){
609
+ if (!blockedGlobalBuiltIns.includes(key)) {
610
+ if (!NON_SANDBOX_GLOBALS.has(key)) {
611
+ if (Object.hasOwn(globalThis, key)) {
612
+ var isWritable = builtin[key];
613
+ Object.defineProperty(scope, key, {
614
+ value: globalThis[key],
615
+ writable: isWritable,
616
+ enumerable: false,
617
+ configurable: false
618
+ });
619
+ }
620
+ }
621
+ }
569
622
  }
570
623
  Object.defineProperty(scope, "globalThis", {
571
624
  value: scope,
@@ -575,6 +628,7 @@ function createGlobalScope() {
575
628
  });
576
629
  return scope;
577
630
  }
631
+ var GLOBAL_SCOPE = createGlobalScope();
578
632
  var getBlockedMethods = function() {
579
633
  var BLOCKED_METHODS = null;
580
634
  return function() {
@@ -629,11 +683,12 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
629
683
  _class_call_check(this, Evaluator);
630
684
  this.maxInputLength = validateLimit(null == options ? void 0 : options.maxInputLength, "maxInputLength", DEFAULT_MAX_INPUT_LENGTH, MAX_CONFIGURABLE_INPUT_LENGTH);
631
685
  this.maxEvaluationSteps = validateLimit(null == options ? void 0 : options.maxEvaluationSteps, "maxEvaluationSteps", DEFAULT_MAX_EVALUATION_STEPS, MAX_CONFIGURABLE_EVALUATION_STEPS);
686
+ this.maxDepth = validateLimit(null == options ? void 0 : options.maxDepth, "maxDepth", DEFAULT_MAX_DEPTH, MAX_CONFIGURABLE_DEPTH);
632
687
  assertBoundedData(variables, "Evaluation context");
633
688
  var clonedVariables = structuredClone(variables);
634
689
  this.scopes = [
635
690
  clonedVariables,
636
- createGlobalScope()
691
+ GLOBAL_SCOPE
637
692
  ].filter(function(scope) {
638
693
  return null != scope;
639
694
  });
@@ -641,6 +696,7 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
641
696
  this.remainingEvaluationSteps = 0;
642
697
  this.remainingDataSize = MAX_BUFFER_BYTES;
643
698
  this.remainingDataNodes = MAX_DATA_NODES;
699
+ this.evaluationDepth = 0;
644
700
  }
645
701
  _create_class(Evaluator, [
646
702
  {
@@ -654,9 +710,16 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
654
710
  this.remainingEvaluationSteps = this.maxEvaluationSteps;
655
711
  this.remainingDataSize = MAX_BUFFER_BYTES;
656
712
  this.remainingDataNodes = MAX_DATA_NODES;
657
- var ast = external_acorn_namespaceObject.parse(expression, {
658
- ecmaVersion: "latest"
659
- });
713
+ this.evaluationDepth = 0;
714
+ var ast;
715
+ try {
716
+ ast = external_acorn_namespaceObject.parse(expression, {
717
+ ecmaVersion: "latest"
718
+ });
719
+ } catch (error) {
720
+ if (_instanceof(error, RangeError) && /call stack/i.test(error.message)) throw new SyntaxError("Expression nesting is too deep to parse");
721
+ throw error;
722
+ }
660
723
  try {
661
724
  return this.execute(ast.body);
662
725
  } finally{
@@ -709,9 +772,24 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
709
772
  {
710
773
  key: "visit",
711
774
  value: function(node) {
712
- var _this = this;
713
775
  this.remainingEvaluationSteps--;
714
776
  if (this.remainingEvaluationSteps < 0) throw new RangeError("Evaluation step limit exceeded");
777
+ this.evaluationDepth++;
778
+ if (this.evaluationDepth > this.maxDepth) {
779
+ this.evaluationDepth--;
780
+ throw new RangeError("Maximum evaluation depth exceeded");
781
+ }
782
+ try {
783
+ return this.visitNode(node);
784
+ } finally{
785
+ this.evaluationDepth--;
786
+ }
787
+ }
788
+ },
789
+ {
790
+ key: "visitNode",
791
+ value: function(node) {
792
+ var _this = this;
715
793
  switch(node.type){
716
794
  case "ExpressionStatement":
717
795
  return this.visit(node.expression);
@@ -797,10 +875,18 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
797
875
  throw new SyntaxError("Unsupported operator: ".concat(node.operator));
798
876
  }
799
877
  },
878
+ {
879
+ key: "assertBoundedRegExp",
880
+ value: function(args) {
881
+ if ("string" == typeof args[0] && args[0].length > MAX_REGEXP_SOURCE_LENGTH) throw new RangeError("RegExp source exceeds the maximum allowed length");
882
+ if ("string" == typeof args[1] && args[1].length > MAX_REGEXP_FLAGS_LENGTH) throw new RangeError("RegExp flags exceed the maximum allowed length");
883
+ }
884
+ },
800
885
  {
801
886
  key: "assertBoundedConstruction",
802
887
  value: function(Constructor, args) {
803
888
  var _args_;
889
+ if (Constructor === RegExp) this.assertBoundedRegExp(args);
804
890
  var size = args[0];
805
891
  if (Constructor === Array && 1 === args.length && "number" == typeof size && size > MAX_ARRAY_LENGTH) throw new RangeError("Array length exceeds the maximum allowed length");
806
892
  if (Constructor === ArrayBuffer && (size > MAX_BUFFER_BYTES || (null == (_args_ = args[1]) ? void 0 : _args_.maxByteLength) > MAX_BUFFER_BYTES)) throw new RangeError("ArrayBuffer size exceeds the maximum allowed size");
@@ -810,7 +896,7 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
810
896
  var collectionSize = "string" == typeof size || Array.isArray(size) ? size.length : null != (_size_size = null == size ? void 0 : size.size) ? _size_size : null == size ? void 0 : size.length;
811
897
  if ("number" == typeof collectionSize && collectionSize > MAX_ARRAY_LENGTH) throw new RangeError("Collection source exceeds the maximum allowed size");
812
898
  }
813
- if (TYPED_ARRAY_CONSTRUCTORS.has(Constructor)) {
899
+ if (isTypedArrayConstructor(Constructor)) {
814
900
  var byteLength;
815
901
  if (_instanceof(size, ArrayBuffer)) {
816
902
  var _args_1;
@@ -905,7 +991,7 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
905
991
  if ("stack" === property && _instanceof(object, Error)) throw new Error(renderErrorMessage(ERROR_MESSAGES.IS_NOT_ALLOWED, [
906
992
  "Accessing error stack"
907
993
  ]));
908
- if (null != object && object[property] === (null == object ? void 0 : object.__proto__)) throw new Error(renderErrorMessage(ERROR_MESSAGES.IS_NOT_ALLOWED, [
994
+ if (null != object && void 0 !== object.__proto__ && object[property] === object.__proto__) throw new Error(renderErrorMessage(ERROR_MESSAGES.IS_NOT_ALLOWED, [
909
995
  "Accessing prototype properties"
910
996
  ]));
911
997
  if (null == object) {
@@ -1047,6 +1133,9 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
1047
1133
  path
1048
1134
  ]));
1049
1135
  }
1136
+ if (TYPED_ARRAY_MUTATORS.has(func)) throw new Error(renderErrorMessage(ERROR_MESSAGES.IS_NOT_ALLOWED, [
1137
+ "TypedArray.prototype.".concat(TYPED_ARRAY_MUTATORS.get(func))
1138
+ ]));
1050
1139
  var args = function() {
1051
1140
  if (0 === node.arguments.length) return [];
1052
1141
  var result = [];
@@ -1088,7 +1177,8 @@ var Evaluator_Evaluator = /*#__PURE__*/ function() {
1088
1177
  if ("string" == typeof args[0] && "" !== args[0] && countLiteralMatches(target, args[0], true) >= MAX_ARRAY_LENGTH) throw new RangeError("Split result exceeds the maximum allowed length");
1089
1178
  }
1090
1179
  }
1091
- if ((target === Array || TYPED_ARRAY_CONSTRUCTORS.has(target)) && func === target.from) {
1180
+ if (func === RegExp) this.assertBoundedRegExp(args);
1181
+ if ((target === Array || isTypedArrayConstructor(target)) && func === target.from) {
1092
1182
  var source = args[0];
1093
1183
  var _source_size;
1094
1184
  var size = "string" == typeof source || Array.isArray(source) ? source.length : null != (_source_size = null == source ? void 0 : source.size) ? _source_size : null == source ? void 0 : source.length;
@@ -1138,7 +1228,7 @@ function getNodeString(node) {
1138
1228
  return node.raw;
1139
1229
  case "ArrayExpression":
1140
1230
  return "[".concat(node.elements.map(function(child) {
1141
- return getNodeString(child);
1231
+ return null === child ? "" : getNodeString(child);
1142
1232
  }).join(","), "]");
1143
1233
  case "ObjectExpression":
1144
1234
  if (0 === node.properties.length) return "{}";
@@ -1211,6 +1301,7 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1211
1301
  var buffer = "";
1212
1302
  var exprStartPos = 0;
1213
1303
  var pos = 0;
1304
+ var prevExprChar = "";
1214
1305
  while(pos < length){
1215
1306
  var char = template[pos];
1216
1307
  var nextChar = template[pos + 1];
@@ -1228,6 +1319,7 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1228
1319
  }
1229
1320
  state = "EXPR";
1230
1321
  exprStartPos = pos;
1322
+ prevExprChar = "";
1231
1323
  pos += this.startLen;
1232
1324
  continue;
1233
1325
  }
@@ -1237,6 +1329,7 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1237
1329
  if ("'" === char) state = "STRING_SQ";
1238
1330
  else if ('"' === char) state = "STRING_DQ";
1239
1331
  else if ("`" === char) state = "TEMPLATE";
1332
+ else if ("/" === char && this._isRegexPosition(prevExprChar)) state = "REGEX";
1240
1333
  else if ("\\" === char) state = "'" === nextChar ? "ESCAPE_SQ" : '"' === nextChar ? "ESCAPE_DQ" : "`" === nextChar ? "ESCAPE_TEMPLATE" : "ESCAPE_EXPR";
1241
1334
  else if (this._isMatch(pos, template, this.exprEnd)) {
1242
1335
  var exprValue = this.preserveWhitespace ? buffer : buffer.trim();
@@ -1253,6 +1346,7 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1253
1346
  pos += this.endLen;
1254
1347
  continue;
1255
1348
  }
1349
+ if (!RegExp("\\s", "u").test(char)) prevExprChar = char;
1256
1350
  buffer += char;
1257
1351
  break;
1258
1352
  case "STRING_SQ":
@@ -1270,6 +1364,17 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1270
1364
  else if ("`" === char) state = "EXPR";
1271
1365
  buffer += char;
1272
1366
  break;
1367
+ case "REGEX":
1368
+ if ("\\" === char) state = "ESCAPE_REGEX";
1369
+ else if ("[" === char) state = "REGEX_CLASS";
1370
+ else if ("/" === char) state = "EXPR";
1371
+ buffer += char;
1372
+ break;
1373
+ case "REGEX_CLASS":
1374
+ if ("\\" === char) state = "ESCAPE_REGEX";
1375
+ else if ("]" === char) state = "REGEX";
1376
+ buffer += char;
1377
+ break;
1273
1378
  case "ESCAPE_SQ":
1274
1379
  buffer += char;
1275
1380
  state = "STRING_SQ";
@@ -1282,6 +1387,10 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1282
1387
  buffer += char;
1283
1388
  state = "TEMPLATE";
1284
1389
  break;
1390
+ case "ESCAPE_REGEX":
1391
+ buffer += char;
1392
+ state = "REGEX";
1393
+ break;
1285
1394
  case "ESCAPE_EXPR":
1286
1395
  buffer += char;
1287
1396
  state = "EXPR";
@@ -1307,6 +1416,12 @@ var TemplateParser_TemplateParser = /*#__PURE__*/ function() {
1307
1416
  return tokens;
1308
1417
  }
1309
1418
  },
1419
+ {
1420
+ key: "_isRegexPosition",
1421
+ value: function(prev) {
1422
+ return "" === prev || "(,=:[!&|?{};+-*%^~<>".includes(prev);
1423
+ }
1424
+ },
1310
1425
  {
1311
1426
  key: "_isMatch",
1312
1427
  value: function(pos, template, sequence) {