Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implemented escaping

  • Loading branch information...
commit ee90dee41e226ee925f6dffb4ff8d6a2c0b5d85f 1 parent a59f589
Anders Hellerup Madsen authored
View
27 template/template.js
@@ -165,9 +165,10 @@ function normalize(value) {
/*************** Context *********************************/
-function Context(o, blockmark) {
- this.scope = [ o ];
+function Context(o) {
+ this.scope = [ o || {} ];
this.blocks = {};
+ this.autoescaping = true;
}
process.mixin(Context.prototype, {
@@ -277,16 +278,32 @@ process.mixin(FilterExpression.prototype, {
value = context.get(this.variable);
}
- return this.filter_list.reduce( function (p,c) {
+ var safety = {
+ is_safe: false,
+ must_escape: context.autoescaping,
+ };
+
+ var out = this.filter_list.reduce( function (p,c) {
+
var filter = template_defaults.filters[c.name];
+
if ( filter && typeof filter === 'function') {
- return filter(p, c.arg);
+ return filter(p, c.arg, safety);
} else {
// throw 'Cannot find filter';
sys.debug('Cannot find filter ' + c.name);
- return value;
+ return p;
}
}, value);
+
+ if (safety.must_escape && !safety.is_safe) {
+ if (typeof out === 'string') {
+ return utils.html.escape(out)
+ } else if (out instanceof Array) {
+ return out.map( utils.html.escape );
+ }
+ }
+ return out;
}
});
View
26 template/template.test.js
@@ -68,6 +68,32 @@ testcase('Filter Expression tests');
shouldThrow(attempt, 'item|add:2 |sub');
});
+ test('output (without filters) should be escaped if autoescaping is on', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = true;
+ var expr = new FilterExpression("test");
+ assertEquals('&lt;script&gt;', expr.resolve(context));
+ });
+
+ test('output (without filters) should not be escaped if autoescaping is off', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = false;
+ var expr = new FilterExpression("test");
+ assertEquals('<script>', expr.resolve(context));
+ });
+ test('safe filter should prevent escaping', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = true;
+ var expr = new FilterExpression("test|safe|upper");
+ assertEquals('<SCRIPT>', expr.resolve(context));
+ });
+ test('escape filter should force escaping', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = false;
+ var expr = new FilterExpression("test|escape|upper");
+ assertEquals('&lt;SCRIPT&gt;', expr.resolve(context));
+ });
+
testcase('Context test');
setup( function () {
var tc = {
View
92 template/template_defaults.js
@@ -85,9 +85,9 @@ var filters = exports.filters = {
},
divisibleby: function (value, arg) { return value % arg === 0; },
- escape: function (value, arg) {
- // TODO: Implement escaping/autoescaping correctly
- throw "escape() filter is not implemented";
+ escape: function (value, arg, safety) {
+ safety.must_escape = true;
+ return value;
},
escapejs: function (value, arg) { return escape(value || ''); },
filesizeformat: function (value, arg) {
@@ -100,8 +100,10 @@ var filters = exports.filters = {
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + 'GB';
},
first: function (value, arg) { return (value instanceof Array) ? value[0] : ""; },
- fix_ampersands: function (value, arg) { return ("" + value).replace('&', '&amp;'); },
-
+ fix_ampersands: function (value, arg, safety) {
+ safety.is_safe = true;
+ return ("" + value).replace('&', '&amp;');
+ },
floatformat: function (value, arg) {
arg = arg || -1;
var num = value - 0,
@@ -116,7 +118,10 @@ var filters = exports.filters = {
}
return s;
},
- force_escape: function (value, arg) { return utils.html.escape("" + value); },
+ force_escape: function (value, arg, safety) {
+ safety.is_safe = true;
+ return utils.html.escape("" + value);
+ },
get_digit: function (value, arg) {
if (typeof value !== 'number' || typeof arg !== 'number' || typeof arg < 1) { return value; }
var s = "" + value;
@@ -128,16 +133,36 @@ var filters = exports.filters = {
},
join: function (value, arg) { return (value instanceof Array) ? value.join(arg) : ''; },
last: function (value, arg) { return ((value instanceof Array) && value.length) ? value[value.length - 1] : ''; },
- length: function (value, arg) { return value.hasOwnProperty('length') ? value.length : 0; },
- length_is: function (value, arg) { return value.hasOwnProperty('length') && value.length === arg; },
- linebreaks: function (value, arg) { return utils.html.linebreaks("" + value); },
- linebreaksbr: function (value, arg) { return "" + value.replace(/\n/g, '<br />'); },
- linenumbers: function (value, arg) {
+ length: function (value, arg) { return value.length ? value.length : 0; },
+ length_is: function (value, arg) { return value.length === arg; },
+ linebreaks: function (value, arg, safety) {
+ if (!safety.is_safe && safety.must_escape) {
+ value = utils.html.escape("" + value);
+ }
+ safety.is_safe = true;
+ return utils.html.linebreaks("" + value);
+ },
+ linebreaksbr: function (value, arg, safety) {
+ if (!safety.is_safe && safety.must_escape) {
+ value = utils.html.escape("" + value);
+ }
+ safety.is_safe = true;
+ return "" + value.replace(/\n/g, '<br />');
+ },
+ linenumbers: function (value, arg, safety) {
var lines = String(value).split('\n');
var len = String(lines.length).length;
- return lines
- .map(function (s, idx) { return utils.string.sprintf('%0' + len + 'd. %s', idx + 1, s); })
+
+ // TODO: escape if string is not safe, and autoescaping is active
+ var out = lines
+ .map(function (s, idx) {
+ if (!safety.is_safe && safety.must_escape) {
+ s = utils.html.escape("" + s);
+ }
+ return utils.string.sprintf('%0' + len + 'd. %s', idx + 1, s); })
.join('\n');
+ safety.is_safe = true;
+ return out;
},
ljust: function (value, arg) {
try {
@@ -165,11 +190,12 @@ var filters = exports.filters = {
},
pprint: function (value, arg) { return JSON.stringify(value); },
random: function (value, arg) {
- return (value instanceof Array) ? value[ Math.floor( Math.random() * 4 ) ] : '';
+ return (value instanceof Array) ? value[ Math.floor( Math.random() * value.length ) ] : '';
},
- removetags: function (value, arg) {
+ removetags: function (value, arg, safety) {
arg = String(arg).replace(/\s+/g, '|');
var re = new RegExp( '</?\\s*(' + arg + ')\\b[^>]*/?>', 'ig');
+ safety.is_safe = true;
return String(value).replace(re, '');
},
rjust: function (value, arg) {
@@ -179,13 +205,13 @@ var filters = exports.filters = {
return '';
}
},
- safe: function (value, arg) {
- // TODO: implement autoescaping
- throw "safe is not implemented";
+ safe: function (value, arg, safety) {
+ safety.is_safe = true;
+ return value;
},
safeseq: function (value, arg) {
- // TODO: implement autoescaping
- throw "safeseq is not implemented";
+ safety.is_safe = true;
+ return value;
},
slice: function (value, arg) {
if (!(value instanceof Array)) { return []; }
@@ -212,7 +238,8 @@ var filters = exports.filters = {
stringformat: function (value, arg) {
try { return utils.string.sprintf('%' + arg, value); } catch (e) { return ''; }
},
- striptags: function (value, arg) {
+ striptags: function (value, arg, safety) {
+ safety.is_safe = true;
return String(value).replace(/<(.|\n)*?>/g, '');
},
title: function (value, arg) {
@@ -236,7 +263,6 @@ var nodes = exports.nodes = {
return function () { return text; };
},
-
VariableNode: function (filterexpression) {
return function (context) {
return filterexpression.resolve(context);
@@ -334,15 +360,17 @@ var nodes = exports.nodes = {
// put this block in front of list
context.blocks[name].unshift( node_list );
- context.push();
+ // if this is a root template descend through templates and evaluate blocks for overrides
+ if (!context.extends) {
+ context.push();
- // descend through templates and evaluate for overrides
- context.blocks[name].forEach( function (list) {
- out = list.evaluate( context );
- context.set('block', { super: out });
- });
+ context.blocks[name].forEach( function (list) {
+ out = list.evaluate( context );
+ context.set('block', { super: out });
+ });
- context.pop();
+ context.pop();
+ }
return out;
};
@@ -490,10 +518,14 @@ var callbacks = exports.callbacks = {
'extends': function (parser, token) {
var parts = template.split_token(token.contents);
- if (parts[0] !== 'extends' || parts.length !== 2) { throw 'unexpected syntax in "block" tag'; }
+ if (parts[0] !== 'extends' || parts.length !== 2) { throw 'unexpected syntax in "extends" tag'; }
var name = parts[1];
return nodes.ExtendsNode(name);
}
};
+
+
+
+
View
87 template/template_defaults.test.js
@@ -96,7 +96,12 @@ testcase('first filter');
testcase('fix_ampersands');
test('should fix ampersands', function () {
- assertEquals('Tom &amp; Jerry', filters.fix_ampersands('Tom & Jerry'));
+ assertEquals('Tom &amp; Jerry', filters.fix_ampersands('Tom & Jerry', null, {}));
+ });
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.fix_ampersands('Tom & Jerry', {}, safety);
+ assertEquals(true, safety.is_safe);
});
testcase('floatformat filter');
@@ -119,9 +124,14 @@ testcase('force_escape filter');
test('should escape string', function () {
assertEquals(
'&lt;script=&qout;alert(&#39;din mor&#39;)&qout;&gt;&lt;/script&gt;',
- filters.force_escape('<script="alert(\'din mor\')"></script>')
+ filters.force_escape('<script="alert(\'din mor\')"></script>', null, {})
);
});
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.force_escape('<script="alert(\'din mor\')"></script>', null, safety)
+ assertEquals(true, safety.is_safe);
+ });
testcase('get_digit');
test('should get correct digit', function () {
@@ -162,11 +172,33 @@ testcase('length_is filter')
});
testcase('linebreaks')
test('linebreaks should be converted to <p> and <br /> tags.', function () {
- assertEquals('<p>Joel<br />is a slug</p>', filters.linebreaks('Joel\nis a slug'));
+ assertEquals('<p>Joel<br />is a slug</p>', filters.linebreaks('Joel\nis a slug', null, {}));
+ });
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.linebreaks('Joel\nis a slug', null, safety)
+ assertEquals(true, safety.is_safe);
+ });
+ test('string should be escaped if requsted', function () {
+ var safety = { must_escape: true };
+ var actual = filters.linebreaks('Two is less than three\n2 < 3', null, safety)
+ assertEquals('<p>Two is less than three<br />2 &lt; 3</p>', actual)
});
testcase('linebreaksbr')
test('linebreaks should be converted to <br /> tags.', function () {
- assertEquals('Joel<br />is a slug.<br />For sure...', filters.linebreaksbr('Joel\nis a slug.\nFor sure...'));
+ assertEquals('Joel<br />is a slug.<br />For sure...',
+ filters.linebreaksbr('Joel\nis a slug.\nFor sure...', null, {})
+ );
+ });
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.linebreaksbr('Joel\nis a slug', null, safety)
+ assertEquals(true, safety.is_safe);
+ });
+ test('string should be escaped if requsted', function () {
+ var safety = { must_escape: true };
+ var actual = filters.linebreaksbr('Two is less than three\n2 < 3', null, safety)
+ assertEquals('Two is less than three<br />2 &lt; 3', actual)
});
testcase('linenumbers')
test('should add linenumbers to text', function () {
@@ -197,7 +229,17 @@ testcase('linenumbers')
+ "11. circumstances occur in which toil and pain can procure him\n"
+ "12. some great pleasure. To take a trivial example, which of us"
- assertEquals(expected, filters.linenumbers(s));
+ assertEquals(expected, filters.linenumbers(s, null, {}));
+ });
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.linenumbers('Joel\nis a slug', null, safety)
+ assertEquals(true, safety.is_safe);
+ });
+ test('string should be escaped if requsted', function () {
+ var safety = { must_escape: true };
+ var actual = filters.linenumbers('Two is less than three\n2 < 3', null, safety)
+ assertEquals('1. Two is less than three\n2. 2 &lt; 3', actual)
});
testcase('ljust')
test('should left justify value i correctly sized field', function () {
@@ -236,6 +278,7 @@ testcase('pprint');
if (!response) { fail('response is empty!'); }
});
testcase('random');
+ // TODO: The testcase for random is pointless and should be improved
test('should return an element from the list', function () {
var arr = ['h', 'e', 's', 't'];
var response = filters.random(arr);
@@ -249,7 +292,12 @@ testcase('random');
testcase('removetags');
test('should remove tags', function () {
assertEquals('Joel <button>is</button> a slug',
- filters.removetags('<b>Joel</b> <button>is</button> a <span\n>slug</span>', 'b span'));
+ filters.removetags('<b>Joel</b> <button>is</button> a <span\n>slug</span>', 'b span', {}));
+ });
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.removetags('<b>Joel</b> <button>is</button> a <span\n>slug</span>', 'b span', safety);
+ assertEquals(true, safety.is_safe);
});
testcase('rjust')
test('should right justify value in correctly sized field', function () {
@@ -286,7 +334,14 @@ testcase('stringformat');
});
testcase('striptags');
test('should remove tags', function () {
- assertEquals('jeg har en dejlig hest.', filters.striptags('<p>jeg har en <strong\n>dejlig</strong> hest.</p>'));
+ assertEquals('jeg har en dejlig hest.',
+ filters.striptags('<p>jeg har en <strong\n>dejlig</strong> hest.</p>', null, {})
+ );
+ });
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.striptags('<p>jeg har en <strong\n>dejlig</strong> hest.</p>', null, safety);
+ assertEquals(true, safety.is_safe);
});
testcase('title');
test('should titlecase correctly', function () {
@@ -304,5 +359,23 @@ testcase('urlencode');
test('should encode urls', function () {
assertEquals('%22Aardvarks%20lurk%2C%20OK%3F%22', filters.urlencode('"Aardvarks lurk, OK?"'));
});
+testcase('safe');
+ test('string should be marked as safe', function () {
+ var safety = {};
+ filters.safe('Joel is a slug', null, safety);
+ assertEquals(true, safety.is_safe);
+ });
+testcase('safeseq');
+ test('output should be marked as safe', function () {
+ var safety = {};
+ filters.safe(['hest', 'giraf'], null, safety);
+ assertEquals(true, safety.is_safe);
+ });
+testcase('escape');
+ test('output should be marked as in need of escaping', function () {
+ var safety = { must_escape: false };
+ filters.escape('hurra', null, safety);
+ assertEquals(true, safety.must_escape);
+ });
run();
Please sign in to comment.
Something went wrong with that request. Please try again.