Permalink
Browse files

Allow escaping for js in escape filter and autoescape tag.

  • Loading branch information...
1 parent 6302494 commit 39a1eee64506efc2ddfdc1396d13cb08c9cb1724 @paularmstrong committed Oct 2, 2011
Showing with 87 additions and 8 deletions.
  1. +10 −1 docs/tags.md
  2. +37 −6 lib/helpers.js
  3. +1 −1 lib/parser.js
  4. +10 −0 tests/filters.test.js
  5. +29 −0 tests/tags.test.js
View
@@ -91,7 +91,10 @@ Also supports using the `{% else %}` and `{% else if ... %}` tags within an if-b
### autoescape
-The `autoescape` tag accepts one of two controls: `on` or `off` (default is `on` if not provided). These either turn variable auto-escaping on or off for the contents of the filter, regardless of the global setting.
+The `autoescape` tag accepts two controls:
+
+1. `on` or `off`: (default is `on` if not provided). These either turn variable auto-escaping on or off for the contents of the filter, regardless of the global setting.
+1. escape-type: optionally include `"js"` to escape variables safe for JavaScript
For the following contexts, assume:
@@ -107,12 +110,18 @@ So the following:
{{ some_html_output }}
{% endautoescape %}
+ {% autoescape on "js" %}
+ {{ some_html_output }}
+ {% endautoescape %}
+
Will output:
<p>Hello "you" & 'them'</p>
&lt;p&gt;Hello &quot;you&quot; &amp; &#39;them&#39; &lt;/p&gt;
+ \u003Cp\u003EHello \u0022you\u0022 & \u0027them\u0027\u003C\u005Cp\u003E
+
### set
It is also possible to set variables in templates.
View
@@ -137,9 +137,41 @@ exports.escape = function (variable, context) {
return chain.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
};
-exports.escaper = function (input) {
+exports.escaper = function (input, type) {
+ type = type || 'html';
if (typeof input === 'string') {
- return input.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
+ if (type === 'js') {
+ var i = 0,
+ code,
+ out = '';
+
+ input = input.replace(/\\/g, '\\u005C');
+
+ for (; i < input.length; i += 1) {
+ code = input.charCodeAt(i);
+ if (code < 32) {
+ code = code.toString(16).toUpperCase();
+ code = (code.length < 2) ? '0' + code : code;
+ out += '\\u00' + code;
+ } else {
+ out += input[i];
+ }
+ }
+
+ return out.replace(/&/g, '\\u0026')
+ .replace(/</g, '\\u003C')
+ .replace(/>/g, '\\u003E')
+ .replace(/\'/g, '\\u0027')
+ .replace(/"/g, '\\u0022')
+ .replace(/\=/g, '\\u003D')
+ .replace(/-/g, '\\u002D')
+ .replace(/;/g, '\\u003B');
+ }
+ return input.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
} else {
return input;
}
@@ -183,7 +215,7 @@ exports.wrapFilters = function (variable, filters, context, escape) {
return;
case 'e':
case 'escape':
- escape = true;
+ escape = (filter.args.length === 1) ? filter.args[0].replace(/\"/g, '') : escape;
return;
default:
output = exports.wrapFilter(output, filter);
@@ -192,10 +224,9 @@ exports.wrapFilters = function (variable, filters, context, escape) {
});
}
+ output = output || '""';
if (escape) {
- output = '__escape.apply(this, [' + output + '])';
- } else {
- output = output || '""';
+ output = '__escape.apply(this, [' + output + ', \'' + escape + '\'])';
}
return output;
View
@@ -209,7 +209,7 @@ exports.parse = function (data, tags, autoescape) {
if (tagname === 'autoescape') {
last_escape = escape;
- escape = (!parts.length || parts[0] === 'on') ? true : false;
+ escape = (!parts.length || parts[0] === 'on') ? ((parts.length >= 2) ? parts[i].replace(/^\"|\'|\'$|\"$/g, '') : true) : false;
}
token = {
View
@@ -96,6 +96,11 @@ exports.e = function (test) {
testFilter(test, 'e', { v: '<&>' }, '&lt;&amp;&gt;', 'Unescaped output');
testFilter(test, 'first|e', { v: ['<&>'] }, '&lt;&amp;&gt;', 'Unescaped in chain');
testFilter(test, 'upper|e|lower', { v: '<&>fOo' }, '&lt;&amp;&gt;foo', 'Unescaped in middle of chain');
+ testFilter(test, 'e("js")', { v: '"double quotes" and \'single quotes\'' }, '\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027');
+ testFilter(test, 'e("js")', { v: '<script>and this</script>' }, '\\u003Cscript\\u003Eand this\\u003C/script\\u003E');
+ testFilter(test, 'e("js")', { v: '\\ : backslashes, too' }, '\\u005C : backslashes, too');
+ testFilter(test, 'e("js")', { v: 'and lots of whitespace: \r\n\t\v\f\b' }, 'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008');
+ testFilter(test, 'e("js")', { v: 'and "special" chars = -1;' }, 'and \\u0022special\\u0022 chars \\u003D \\u002D1\\u003B');
swig.init({});
test.done();
};
@@ -105,6 +110,11 @@ exports.escape = function (test) {
testFilter(test, 'escape', { v: '<&>' }, '&lt;&amp;&gt;', 'Unescaped output');
testFilter(test, 'first|escape', { v: ['<&>'] }, '&lt;&amp;&gt;', 'Unescaped in chain');
testFilter(test, 'upper|escape|lower', { v: '<&>fOo' }, '&lt;&amp;&gt;foo', 'Unescaped in middle of chain');
+ testFilter(test, 'escape("js")', { v: '"double quotes" and \'single quotes\'' }, '\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027');
+ testFilter(test, 'escape("js")', { v: '<script>and this</script>' }, '\\u003Cscript\\u003Eand this\\u003C/script\\u003E');
+ testFilter(test, 'escape("js")', { v: '\\ : backslashes, too' }, '\\u005C : backslashes, too');
+ testFilter(test, 'escape("js")', { v: 'and lots of whitespace: \r\n\t\v\f\b' }, 'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008');
+ testFilter(test, 'escape("js")', { v: 'and "special" chars = -1;' }, 'and \\u0022special\\u0022 chars \\u003D \\u002D1\\u003B');
swig.init({});
test.done();
};
View
@@ -19,6 +19,35 @@ exports['custom tags'] = function (test) {
test.done();
};
+exports.autoescape = testCase({
+ setUp: function (callback) {
+ swig.init({});
+ callback();
+ },
+
+ on: function (test) {
+ var tpl = swig.fromString('{% autoescape on %}{{ foo }}{% endautoescape %}');
+ test.strictEqual(tpl.render({ foo: '<\'single\' & "double" quotes>' }), '&lt;&#39;single&#39; &amp; &quot;double&quot; quotes&gt;');
+ test.done();
+ },
+
+ off: function (test) {
+ var tpl = swig.fromString('{% autoescape off %}{{ foo }}{% endautoescape %}');
+ test.strictEqual(tpl.render({ foo: '<\'single\' & "double" quotes>' }), '<\'single\' & "double" quotes>');
+ test.done();
+ },
+
+ js: function (test) {
+ var tpl = swig.fromString('{% autoescape on "js" %}{{ foo }}{% endautoescape %}');
+ test.strictEqual(tpl.render({ foo: '"double quotes" and \'single quotes\'' }), '\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027');
+ test.strictEqual(tpl.render({ foo: '<script>and this</script>' }), '\\u003Cscript\\u003Eand this\\u003C/script\\u003E');
+ test.strictEqual(tpl.render({ foo: '\\ : backslashes, too' }), '\\u005C : backslashes, too');
+ test.strictEqual(tpl.render({ foo: 'and lots of whitespace: \r\n\t\v\f\b' }), 'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008');
+ test.strictEqual(tpl.render({ foo: 'and "special" chars = -1;' }), 'and \\u0022special\\u0022 chars \\u003D \\u002D1\\u003B');
+ test.done();
+ }
+});
+
exports.extends = testCase({
setUp: function (callback) {
swig.init({ root: __dirname + '/templates' });

0 comments on commit 39a1eee

Please sign in to comment.