@rhinostone/swig-core 2.8.0 → 2.8.2

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/lib/backend.js CHANGED
@@ -762,7 +762,7 @@ function checkDangerousSegment(segment, d, node) {
762
762
  if (d.dangerousProps.indexOf(segment) !== -1) {
763
763
  var line = (node && node.loc && node.loc.line) || undefined;
764
764
  var filename = (node && node.loc && node.loc.filename) || undefined;
765
- d.throwError('Unsafe access to "' + segment + '" is not allowed in templates (CVE-2023-25345)', line, filename);
765
+ d.throwError('Unsafe access to "' + segment + '" is not allowed in templates (prototype-chain guard)', line, filename);
766
766
  }
767
767
  }
768
768
 
@@ -880,7 +880,7 @@ function emitAccess(node, d) {
880
880
  if (node.key && node.key.type === 'Literal' && node.key.kind === 'string') {
881
881
  checkDangerousSegment(node.key.value, d, node);
882
882
  }
883
- return emitExpr(node.object, d) + '[' + emitExpr(node.key, d) + ']';
883
+ return groupOperand(node.object, emitExpr(node.object, d), true) + '[' + emitExpr(node.key, d) + ']';
884
884
  }
885
885
 
886
886
  /*!
@@ -893,9 +893,24 @@ function isArithmeticOp(op) {
893
893
  return op === '+' || op === '-' || op === '*' || op === '/' || op === '%';
894
894
  }
895
895
 
896
+ /*!
897
+ * Parenthesize an operand whose emitted JS could regroup. The IR tree
898
+ * carries the grouping (`(a + b) * c` parses to a `*` node whose left
899
+ * operand is the `+` node), so a nested binary node is wrapped or JS
900
+ * precedence and associativity regroup it (`a + b * c`). `unary` also
901
+ * wraps a unary operand, for the positions where its prefix would bind
902
+ * differently (`-(-x)`, `(-x)[0]`). @private
903
+ */
904
+ function groupOperand(node, js, unary) {
905
+ if (node && (node.type === 'BinaryOp' || (unary && node.type === 'UnaryOp'))) {
906
+ return '(' + js + ')';
907
+ }
908
+ return js;
909
+ }
910
+
896
911
  function emitBinaryOp(node, d) {
897
- var left = emitExpr(node.left, d),
898
- right = emitExpr(node.right, d);
912
+ var left = groupOperand(node.left, emitExpr(node.left, d)),
913
+ right = groupOperand(node.right, emitExpr(node.right, d));
899
914
  if (isArithmeticOp(node.op)) {
900
915
  return left + ' ' + node.op + ' ' + right;
901
916
  }
@@ -922,11 +937,7 @@ function emitBinaryOp(node, d) {
922
937
  }
923
938
 
924
939
  function emitUnaryOp(node, d) {
925
- var operandJS = emitExpr(node.operand, d);
926
- if (node.operand && node.operand.type === 'BinaryOp') {
927
- operandJS = '(' + operandJS + ')';
928
- }
929
- return node.op + operandJS;
940
+ return node.op + groupOperand(node.operand, emitExpr(node.operand, d), true);
930
941
  }
931
942
 
932
943
  function emitConditional(node, d) {
package/lib/engine.js CHANGED
@@ -21,6 +21,21 @@ var AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
21
21
  */
22
22
  function efn() { return ''; }
23
23
 
24
+ /**
25
+ * Cache key for a compiled template. An async-mode compile returns a
26
+ * different function shape (a Promise of `{ output, exports }`), so it is
27
+ * stored under its own key: neither mode is served, or replaces, the other
28
+ * mode's entry for the same file.
29
+ *
30
+ * @param {string} filename Template identifier (the resolved path for files).
31
+ * @param {object} [options] Compile options; `codegenMode` is read.
32
+ * @return {string} Cache key.
33
+ * @private
34
+ */
35
+ function compileCacheKey(filename, options) {
36
+ return (options && options.codegenMode === 'async') ? filename + '#async' : filename;
37
+ }
38
+
24
39
  /**
25
40
  * Runtime engine plumbing shared across @rhinostone/swig-family frontends.
26
41
  *
@@ -537,7 +552,7 @@ exports.install = function (self, frontend) {
537
552
  };
538
553
 
539
554
  self.compile = function (source, options) {
540
- var key = options ? options.filename : null,
555
+ var key = (options && options.filename) ? compileCacheKey(options.filename, options) : null,
541
556
  cached = key ? cacheGet(key, options) : null,
542
557
  context,
543
558
  contextLength,
@@ -591,7 +606,7 @@ exports.install = function (self, frontend) {
591
606
  if (!options.filename) {
592
607
  options = utils.extend({ filename: pathname }, options);
593
608
  }
594
- cached = cacheGet(pathname, options);
609
+ cached = cacheGet(compileCacheKey(pathname, options), options);
595
610
 
596
611
  if (cached) {
597
612
  if (cb) {
@@ -641,11 +656,9 @@ exports.install = function (self, frontend) {
641
656
  * load a child template at render time.
642
657
  *
643
658
  * Cache semantics — bypasses the cache via `options.cache = false` on
644
- * the inner compile call. The sync compile cache and the async compile
645
- * cache would otherwise share a key (the resolved filename) and serve
646
- * a mode-mismatched compiled fn to the wrong caller. Cleaner
647
- * namespacing (separate sync/async cache buckets, or a key suffix) is
648
- * deferred to a follow-up slice.
659
+ * the inner compile call. `compile` and `compileFile` keep async-mode
660
+ * entries under their own key (see `compileCacheKey`), so a compile in
661
+ * either mode is never served the other mode's function.
649
662
  *
650
663
  * @param {string} pathname Template path; resolved via the active loader.
651
664
  * @param {object} [options] Per-call options. Honored: `resolveFrom`,
package/lib/index.js CHANGED
@@ -7,6 +7,16 @@
7
7
  * @rhinostone/swig.
8
8
  */
9
9
 
10
+ /**
11
+ * Package version number as a string. The packages of the family are
12
+ * released together, so they share one version.
13
+ * @example
14
+ * if (require('@rhinostone/swig-core').version === "2.8.2") { ... }
15
+ *
16
+ * @type {String}
17
+ */
18
+ exports.version = "2.8.2";
19
+
10
20
  exports.utils = require('./utils');
11
21
  exports.security = require('./security');
12
22
  exports.ir = require('./ir');
@@ -166,7 +166,7 @@ TokenParser.prototype = {
166
166
  if (lastState === _t.BRACKETOPEN) {
167
167
  var strippedMatch = match.replace(/^['"]|['"]$/g, '');
168
168
  if (_dangerousProps.indexOf(strippedMatch) !== -1) {
169
- utils.throwError('Unsafe access to "' + strippedMatch + '" via bracket notation is not allowed in templates (CVE-2023-25345)', self.line, self.filename);
169
+ utils.throwError('Unsafe access to "' + strippedMatch + '" via bracket notation is not allowed in templates (prototype-chain guard)', self.line, self.filename);
170
170
  }
171
171
  }
172
172
  self.filterApplyIdx.push(self.out.length);
@@ -372,7 +372,7 @@ TokenParser.prototype = {
372
372
  }
373
373
  // CVE-2023-25345: block prototype-chain traversal via dot notation
374
374
  if (_dangerousProps.indexOf(match) !== -1) {
375
- utils.throwError('Unsafe access to "' + match + '" is not allowed in templates (CVE-2023-25345)', self.line, self.filename);
375
+ utils.throwError('Unsafe access to "' + match + '" is not allowed in templates (prototype-chain guard)', self.line, self.filename);
376
376
  }
377
377
  self.out.push('.' + match);
378
378
  break;
@@ -404,7 +404,7 @@ TokenParser.prototype = {
404
404
  // CVE-2023-25345: block prototype-chain property access
405
405
  utils.each(match, function (segment) {
406
406
  if (_dangerousProps.indexOf(segment) !== -1) {
407
- utils.throwError('Unsafe access to "' + segment + '" is not allowed in templates (CVE-2023-25345)', self.line, self.filename);
407
+ utils.throwError('Unsafe access to "' + segment + '" is not allowed in templates (prototype-chain guard)', self.line, self.filename);
408
408
  }
409
409
  });
410
410
 
@@ -472,12 +472,12 @@ TokenParser.prototype = {
472
472
  }
473
473
  function guardSegment(segment) {
474
474
  if (_dangerousProps.indexOf(segment) !== -1) {
475
- bail('Unsafe access to "' + segment + '" is not allowed in templates (CVE-2023-25345)');
475
+ bail('Unsafe access to "' + segment + '" is not allowed in templates (prototype-chain guard)');
476
476
  }
477
477
  }
478
478
  function guardBracketString(value) {
479
479
  if (_dangerousProps.indexOf(value) !== -1) {
480
- bail('Unsafe access to "' + value + '" via bracket notation is not allowed in templates (CVE-2023-25345)');
480
+ bail('Unsafe access to "' + value + '" via bracket notation is not allowed in templates (prototype-chain guard)');
481
481
  }
482
482
  }
483
483
 
package/lib/utils.js CHANGED
@@ -189,9 +189,9 @@ exports.throwError = function (message, line, file) {
189
189
  * Inclusive range generator. Mirrors Twig's `..` operator semantics and
190
190
  * PHP's range(): numeric bounds produce [from, from±1, ..., to]; single-
191
191
  * character string bounds produce a char array across the charCode span.
192
- * Descending when `from > to`. Mismatched or unsupported argument shapes
193
- * return an empty array so compiled templates degrade silently rather
194
- * than throwing at render time.
192
+ * Descending when `from > to`. Mismatched or unsupported argument shapes,
193
+ * and a numeric bound that is not finite, return an empty array so
194
+ * compiled templates degrade silently rather than throwing at render time.
195
195
  *
196
196
  * @param {number|string} from
197
197
  * @param {number|string} to
@@ -200,6 +200,13 @@ exports.throwError = function (message, line, file) {
200
200
  exports.range = function (from, to) {
201
201
  var out = [], i, fc, tc;
202
202
  if (typeof from === 'number' && typeof to === 'number') {
203
+ // An infinite bound is never reached, so the loop below ran until the
204
+ // array overflowed (seconds, and gigabytes, before a RangeError); plain
205
+ // JSON yields Infinity for a number such as 1e999. PHP 8.3's range()
206
+ // rejects such a bound; the range is empty here, as for NaN.
207
+ if (!isFinite(from) || !isFinite(to)) {
208
+ return out;
209
+ }
203
210
  if (from <= to) {
204
211
  for (i = from; i <= to; i += 1) { out.push(i); }
205
212
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhinostone/swig-core",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Shared IR, backend, and runtime for the @rhinostone/swig family of template engines.",
5
5
  "keywords": [
6
6
  "template",