@rhinostone/swig-core 2.8.1 → 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 +20 -9
- package/lib/engine.js +20 -7
- package/lib/index.js +10 -0
- package/lib/tokenparser.js +5 -5
- package/lib/utils.js +10 -3
- package/package.json +1 -1
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 (
|
|
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
|
-
|
|
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.
|
|
645
|
-
*
|
|
646
|
-
*
|
|
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');
|
package/lib/tokenparser.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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 {
|