@rhinostone/swig 2.8.3 → 2.8.4
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/HISTORY.md +7 -0
- package/README.md +3 -3
- package/ROADMAP.md +6 -0
- package/dist/swig.js +36 -9
- package/dist/swig.min.js +15 -2
- package/dist/swig.min.js.map +1 -1
- package/dist/swig.runtime.js +2 -2
- package/dist/swig.runtime.min.js +2 -2
- package/lib/parser.js +30 -2
- package/lib/runtime.js +1 -1
- package/lib/swig.js +2 -2
- package/package.json +2 -2
package/HISTORY.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
[2.8.4](https://github.com/gina-io/swig/tree/v2.8.4) / 2026-10-21
|
|
2
|
+
-----------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
* **Fixed** A filename option that is not a string, such as a number, compiles again with import, from and extends on the async render path, as it did in 2.8.2, and include, which failed with a number in 2.8.2 as well, now compiles too. 2.8.3 escaped the filename as a string before writing it into the compiled template, so a number failed with value.replace is not a function; the filename is now converted to a string first. All four frontends.
|
|
5
|
+
|
|
6
|
+
* **Fixed** The autoescape option of @rhinostone/swig now takes the name of an escape strategy, as documented: autoescape set to 'js' gives JavaScript escaping and 'html' HTML escaping. The option was written into the compiled template as code, so these values failed with ReferenceError: js is not defined, and an autoescape option built from untrusted input could run as code in the template. A value already wrapped in quotes keeps working, and a value holding a double quote or a line break is now rejected with an error. The Twig, Jinja2 and Django frontends already passed the option to the escape filter as a string.
|
|
7
|
+
|
|
1
8
|
[2.8.3](https://github.com/gina-io/swig/tree/v2.8.3) / 2026-10-21
|
|
2
9
|
-----------------------------------------------------------------
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -43,12 +43,12 @@ Features
|
|
|
43
43
|
* Robust without the bloat — a single runtime dependency ([`@rhinostone/swig-core`](https://www.npmjs.com/package/@rhinostone/swig-core)).
|
|
44
44
|
* Extendable and customizable — register custom filters, tags, and loaders per-instance.
|
|
45
45
|
|
|
46
|
-
What's new in v2.8.
|
|
46
|
+
What's new in v2.8.4
|
|
47
47
|
--------------------
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
* **Fixed — a string `autoescape` option names the escape strategy.** In `@rhinostone/swig`, `autoescape: 'js'` now gives JavaScript escaping and `autoescape: 'html'` HTML escaping, as documented. The option was written into the compiled template as code, so these values failed with `ReferenceError: js is not defined`, and an autoescape option built from untrusted input could run as code in the template. A value already wrapped in quotes keeps working, and a value holding a double quote or a line break is now rejected with an error. The Twig, Jinja2 and Django frontends already passed the option to the escape filter as a string.
|
|
50
50
|
|
|
51
|
-
* **
|
|
51
|
+
* **Fixed — a numeric `filename` option compiles again.** A filename that is not a string, such as a number, compiles again with `import`, `from` and `extends` on the async render path, as it did in 2.8.2, and `include`, which failed with a number in 2.8.2 as well, now compiles too. 2.8.3 escaped the filename as a string and failed with `value.replace is not a function`; it is now converted to a string first. All four frontends.
|
|
52
52
|
|
|
53
53
|
See the full [HISTORY.md](./HISTORY.md) and [ROADMAP.md](./ROADMAP.md).
|
|
54
54
|
|
package/ROADMAP.md
CHANGED
|
@@ -27,6 +27,12 @@ For bug reports and feature requests, file an issue at [gina-io/swig](https://gi
|
|
|
27
27
|
|
|
28
28
|
## Completed
|
|
29
29
|
|
|
30
|
+
### v2.8.4 (October 2026)
|
|
31
|
+
|
|
32
|
+
- Fixed the `autoescape` option of `@rhinostone/swig` set to an escape strategy name: `'js'` and `'html'` work as documented. The value was written into the compiled template as code, so these failed with a ReferenceError and an option built from untrusted input could run as code; a value holding a double quote or a line break is now rejected with an error.
|
|
33
|
+
- Fixed a `filename` option that is not a string, such as a number, which 2.8.3 no longer compiled with `import`, `from` and `extends` on the async render path; `include` now compiles with a number too. All four frontends.
|
|
34
|
+
- All five packages released in lockstep at `2.8.4`.
|
|
35
|
+
|
|
30
36
|
### v2.8.3 (October 2026)
|
|
31
37
|
|
|
32
38
|
- Security: the template compiler escapes the including template's filename before writing it into the compiled function body, so a double quote in a `filename` option can no longer break out of the generated string literal (GHSA-q89c). Escaping is centralized in the code generator, which also fixes a latent broken-JS bug for backslash paths through `import`, `from` and `extends`.
|
package/dist/swig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Swig v2.8.
|
|
1
|
+
/*! Swig v2.8.4 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
|
|
2
2
|
/*! DateZ (c) 2011 Tomo Universalis | @license https://github.com/tn0ms/DateZ/blob/master/LISENCE */
|
|
3
3
|
(() => {
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1618,7 +1618,7 @@
|
|
|
1618
1618
|
} else {
|
|
1619
1619
|
incSelector = "_utils.extend({}, _ctx, " + incCtxJS + ")";
|
|
1620
1620
|
}
|
|
1621
|
-
out += (node.ignoreMissing ? " try {\n" : "") + "_output += _swig.compileFile(" + incPathJS + ', {resolveFrom: "' + escapeTextValue(node.resolveFrom || "") + '"})(' + incSelector + ");\n" + (node.ignoreMissing ? "} catch (e) {}\n" : "");
|
|
1621
|
+
out += (node.ignoreMissing ? " try {\n" : "") + "_output += _swig.compileFile(" + incPathJS + ', {resolveFrom: "' + escapeTextValue(String(node.resolveFrom || "")) + '"})(' + incSelector + ");\n" + (node.ignoreMissing ? "} catch (e) {}\n" : "");
|
|
1622
1622
|
return;
|
|
1623
1623
|
}
|
|
1624
1624
|
if (node.type === "IncludeDeferred") {
|
|
@@ -1643,7 +1643,7 @@
|
|
|
1643
1643
|
} else {
|
|
1644
1644
|
incdSelector = "_utils.extend({}, _ctx, " + incdCtxJS + ")";
|
|
1645
1645
|
}
|
|
1646
|
-
var incdOpts = '{resolveFrom: "' + escapeTextValue(node.resolveFrom || "") + '"}';
|
|
1646
|
+
var incdOpts = '{resolveFrom: "' + escapeTextValue(String(node.resolveFrom || "")) + '"}';
|
|
1647
1647
|
out += (node.ignoreMissing ? " try {\n" : "") + "_output += (await (await _swig.getTemplate(" + incdPathJS + ", " + incdOpts + "))(" + incdSelector + ")).output;\n" + (node.ignoreMissing ? "} catch (e) {}\n" : "");
|
|
1648
1648
|
return;
|
|
1649
1649
|
}
|
|
@@ -1657,7 +1657,7 @@
|
|
|
1657
1657
|
} else {
|
|
1658
1658
|
impdPathJS = node.path;
|
|
1659
1659
|
}
|
|
1660
|
-
var impdOpts = '{resolveFrom: "' + escapeTextValue(node.resolveFrom || "") + '"}';
|
|
1660
|
+
var impdOpts = '{resolveFrom: "' + escapeTextValue(String(node.resolveFrom || "")) + '"}';
|
|
1661
1661
|
out += "_ctx." + node.alias + " = (await (await _swig.getTemplate(" + impdPathJS + ", " + impdOpts + "))(_ctx)).exports;\n";
|
|
1662
1662
|
return;
|
|
1663
1663
|
}
|
|
@@ -1678,7 +1678,7 @@
|
|
|
1678
1678
|
} else {
|
|
1679
1679
|
fromdPathJS = node.path;
|
|
1680
1680
|
}
|
|
1681
|
-
var fromdOpts = '{resolveFrom: "' + escapeTextValue(node.resolveFrom || "") + '"}';
|
|
1681
|
+
var fromdOpts = '{resolveFrom: "' + escapeTextValue(String(node.resolveFrom || "")) + '"}';
|
|
1682
1682
|
var fromdBindings = "";
|
|
1683
1683
|
utils.each(fromdImports, function(entry) {
|
|
1684
1684
|
var bindName = entry.alias || entry.name;
|
|
@@ -1715,7 +1715,7 @@
|
|
|
1715
1715
|
} else {
|
|
1716
1716
|
extPathJS = node.path;
|
|
1717
1717
|
}
|
|
1718
|
-
var extOpts = '{resolveFrom: "' + escapeTextValue(node.resolveFrom || "") + '"}';
|
|
1718
|
+
var extOpts = '{resolveFrom: "' + escapeTextValue(String(node.resolveFrom || "")) + '"}';
|
|
1719
1719
|
out += "var _parentTpl = await _swig.getTemplate(" + extPathJS + ", " + extOpts + ");\n";
|
|
1720
1720
|
out += "var _parentResult = await _parentTpl(_ctx, _mergedBlocks);\n";
|
|
1721
1721
|
out += "_output = _parentResult.output;\n";
|
|
@@ -3058,9 +3058,23 @@
|
|
|
3058
3058
|
function escapeRegExp(str) {
|
|
3059
3059
|
return str.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, "\\$&");
|
|
3060
3060
|
}
|
|
3061
|
+
function quoteEscapeOption(value) {
|
|
3062
|
+
var quote;
|
|
3063
|
+
if (typeof value !== "string" || value === "") {
|
|
3064
|
+
return value;
|
|
3065
|
+
}
|
|
3066
|
+
quote = value.charAt(0);
|
|
3067
|
+
if (value.length > 1 && (quote === '"' || quote === "'") && value.charAt(value.length - 1) === quote) {
|
|
3068
|
+
value = value.slice(1, -1);
|
|
3069
|
+
}
|
|
3070
|
+
if (/["\n\r\u2028\u2029]/.test(value)) {
|
|
3071
|
+
throw new Error('Option "autoescape" must be true, false or an escape strategy name without double quotes or line breaks.');
|
|
3072
|
+
}
|
|
3073
|
+
return '"' + value + '"';
|
|
3074
|
+
}
|
|
3061
3075
|
exports.parse = function(swig2, source, opts, tags, filters) {
|
|
3062
3076
|
source = source.replace(/\r\n/g, "\n");
|
|
3063
|
-
var escape = opts.autoescape, tagOpen = opts.tagControls[0], tagClose = opts.tagControls[1], varOpen = opts.varControls[0], varClose = opts.varControls[1], escapedTagOpen = escapeRegExp(tagOpen), escapedTagClose = escapeRegExp(tagClose), escapedVarOpen = escapeRegExp(varOpen), escapedVarClose = escapeRegExp(varClose), tagStrip = new RegExp("^" + escapedTagOpen + "-?\\s*|\\s*-?" + escapedTagClose + "$", "g"), tagStripBefore = new RegExp("^" + escapedTagOpen + "-"), tagStripAfter = new RegExp("-" + escapedTagClose + "$"), varStrip = new RegExp("^" + escapedVarOpen + "-?\\s*|\\s*-?" + escapedVarClose + "$", "g"), varStripBefore = new RegExp("^" + escapedVarOpen + "-"), varStripAfter = new RegExp("-" + escapedVarClose + "$"), cmtOpen = opts.cmtControls[0], cmtClose = opts.cmtControls[1], anyChar = "[\\s\\S]*?", splitter = new RegExp(
|
|
3077
|
+
var escape = quoteEscapeOption(opts.autoescape), tagOpen = opts.tagControls[0], tagClose = opts.tagControls[1], varOpen = opts.varControls[0], varClose = opts.varControls[1], escapedTagOpen = escapeRegExp(tagOpen), escapedTagClose = escapeRegExp(tagClose), escapedVarOpen = escapeRegExp(varOpen), escapedVarClose = escapeRegExp(varClose), tagStrip = new RegExp("^" + escapedTagOpen + "-?\\s*|\\s*-?" + escapedTagClose + "$", "g"), tagStripBefore = new RegExp("^" + escapedTagOpen + "-"), tagStripAfter = new RegExp("-" + escapedTagClose + "$"), varStrip = new RegExp("^" + escapedVarOpen + "-?\\s*|\\s*-?" + escapedVarClose + "$", "g"), varStripBefore = new RegExp("^" + escapedVarOpen + "-"), varStripAfter = new RegExp("-" + escapedVarClose + "$"), cmtOpen = opts.cmtControls[0], cmtClose = opts.cmtControls[1], anyChar = "[\\s\\S]*?", splitter = new RegExp(
|
|
3064
3078
|
"(" + escapedTagOpen + anyChar + escapedTagClose + "|" + escapedVarOpen + anyChar + escapedVarClose + "|" + escapeRegExp(cmtOpen) + anyChar + escapeRegExp(cmtClose) + ")"
|
|
3065
3079
|
), line = 1, stack = [], parent = null, parentExpr = null, tokens = [], blocks = {}, inRaw = false, stripNext;
|
|
3066
3080
|
function parseVariable(str, line2) {
|
|
@@ -3080,7 +3094,7 @@
|
|
|
3080
3094
|
if (last && last.name === str.split(/\s+/)[0].replace(/^end/, "") && last.ends) {
|
|
3081
3095
|
switch (last.name) {
|
|
3082
3096
|
case "autoescape":
|
|
3083
|
-
escape = opts.autoescape;
|
|
3097
|
+
escape = quoteEscapeOption(opts.autoescape);
|
|
3084
3098
|
break;
|
|
3085
3099
|
case "raw":
|
|
3086
3100
|
inRaw = false;
|
|
@@ -4822,7 +4836,7 @@
|
|
|
4822
4836
|
var loaders = require_loaders2();
|
|
4823
4837
|
var preWalker = require_pre_walker();
|
|
4824
4838
|
var engine = require_engine();
|
|
4825
|
-
exports.version = "2.8.
|
|
4839
|
+
exports.version = "2.8.4";
|
|
4826
4840
|
var defaultOptions = {
|
|
4827
4841
|
autoescape: true,
|
|
4828
4842
|
varControls: ["{{", "}}"],
|
|
@@ -5185,6 +5199,19 @@
|
|
|
5185
5199
|
* @return {string}
|
|
5186
5200
|
* @private
|
|
5187
5201
|
*/
|
|
5202
|
+
/*!
|
|
5203
|
+
* Turn a string autoescape option into the string literal that the
|
|
5204
|
+
* autoescape tail writes into the compiled template as the e filter's
|
|
5205
|
+
* strategy argument; the bare option used to be written there as code.
|
|
5206
|
+
* The tail doubles every backslash, so the value is wrapped in double
|
|
5207
|
+
* quotes without escaping it, and a value holding a double quote or a line
|
|
5208
|
+
* break is rejected. One pair of matching outer quotes is removed first,
|
|
5209
|
+
* so a value that was already quoted keeps working. A value that is not a
|
|
5210
|
+
* non-empty string is returned unchanged.
|
|
5211
|
+
* @param {*} value The autoescape option.
|
|
5212
|
+
* @return {*}
|
|
5213
|
+
* @private
|
|
5214
|
+
*/
|
|
5188
5215
|
/*!
|
|
5189
5216
|
* Loop over the source, split via the tag/var/comment regular expression splitter.
|
|
5190
5217
|
* Send each chunk to the appropriate parser.
|