Skip to content
This repository has been archived by the owner on Sep 5, 2018. It is now read-only.

decoupled from depending on exphbs.compileTemplate #2

Merged
merged 1 commit into from
Mar 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "express-secure-handlebars",
"version": "1.0.4",
"version": "1.1.0",
"licenses": [
{
"type": "BSD",
Expand Down Expand Up @@ -32,10 +32,10 @@
"test": "grunt test"
},
"dependencies": {
"util": "^0.10.*",
"debug": "^2.1.*",
"context-parser-handlebars": "^1.0.5",
"express-handlebars": "^1.2.*",
"handlebars": "^3.0.0",
"util": "^0.10.*",
"xss-filters": "^1.*"
},
"devDependencies": {
Expand Down
58 changes: 6 additions & 52 deletions src/express-secure-handlebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,68 +9,22 @@ Authors: Nera Liu <[email protected]>
*/
/*jshint -W030 */
var util = require("util"),
debug = require('debug')('esh'),
expressHandlebars = require('express-handlebars').ExpressHandlebars,
xssFilters = require('xss-filters'),
privateFilters = xssFilters._privFilters,
ContextParserHandlebars = require("context-parser-handlebars");
secureHandlebars = require('./secure-handlebars');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good! we can simply create the secure-handlebars and replace this line later.


function ExpressSecureHandlebars(config) {

// override the original handlebars with secure-handlebars
config || (config = {});
config.handlebars = config.secureHandlebars || secureHandlebars;

/* calling super constructor */
this.constructor.super_.call(this, config);

if (this.handlebars.helpers) {
var h = this.handlebars;
// register below the filters that are automatically applied by context parser
[
'y',
'yd', 'yc',
'yavd', 'yavs', 'yavu',
'yu', 'yuc',
'yubl', 'yufull'
].forEach(function(filterName){
h.registerHelper(filterName, privateFilters[filterName]);
});
// register below the filters that might be manually applied by developers
[
'inHTMLData', 'inHTMLComment',
'inSingleQuotedAttr', 'inDoubleQuotedAttr', 'inUnQuotedAttr',
'uriInSingleQuotedAttr', 'uriInDoubleQuotedAttr', 'uriInUnQuotedAttr', 'uriInHTMLData', 'uriInHTMLComment',
'uriPathInSingleQuotedAttr', 'uriPathInDoubleQuotedAttr', 'uriPathInUnQuotedAttr', 'uriPathInHTMLData', 'uriPathInHTMLComment',
'uriQueryInSingleQuotedAttr', 'uriQueryInDoubleQuotedAttr', 'uriQueryInUnQuotedAttr', 'uriQueryInHTMLData', 'uriQueryInHTMLComment',
'uriComponentInSingleQuotedAttr', 'uriComponentInDoubleQuotedAttr', 'uriComponentInUnQuotedAttr', 'uriComponentInHTMLData', 'uriComponentInHTMLComment',
'uriFragmentInSingleQuotedAttr', 'uriFragmentInDoubleQuotedAttr', 'uriFragmentInUnQuotedAttr', 'uriFragmentInHTMLData', 'uriFragmentInHTMLComment'
].forEach(function(filterName){
h.registerHelper(filterName, xssFilters[filterName]);
});
debug(h.helpers);
}
}

/* inheriting the express-handlebars */
util.inherits(ExpressSecureHandlebars, expressHandlebars);

ExpressSecureHandlebars.prototype.compileTemplate = function (template, options) {
if (!options || !options.precompiled) {
try {
var parser = new ContextParserHandlebars({printCharEnable: false});
parser.contextualize(template);
template = parser.getOutput();
} catch (err) {
console.log('=====================');
console.log("[WARNING] ExpressSecureHandlebars: falling back to the original express-handlebars");
Object.keys(err).forEach(function(k){console.log(k.toUpperCase() + ':\n' + err[k]);});
console.log("TEMPLATE:\n" + template);
console.log('=====================');
}
} else {
console.log("[WARNING] ExpressSecureHandlebars: ContextParserHandlebars cannot handle precompiled template!");
}

return ExpressSecureHandlebars.super_.prototype.compileTemplate.call(this, template, options);
};

/* exporting the same signature of express-handlebars */
exports = module.exports = exphbs;
exports.create = create;
Expand All @@ -84,4 +38,4 @@ function exphbs(config) {

function create(config) {
return new ExpressSecureHandlebars(config);
}
}
80 changes: 80 additions & 0 deletions src/secure-handlebars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright (c) 2015, Yahoo Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.

Authors: Nera Liu <[email protected]>
Albert Yu <[email protected]>
Adonis Fung <[email protected]>
*/
/*jshint -W030 */
var Handlebars = require('handlebars'),
ContextParserHandlebars = require("context-parser-handlebars"),
xssFilters = require('xss-filters');



function preprocess(template) {
try {
if (template) {
var parser = new ContextParserHandlebars({printCharEnable: false});
parser.contextualize(template);
return parser.getOutput();
}
} catch (err) {
console.log('=====================');
console.log("[WARNING] SecureHandlebars: falling back to the original template");
Object.keys(err).forEach(function(k){console.log(k.toUpperCase() + ':\n' + err[k]);});
console.log("TEMPLATE:\n" + template);
console.log('=====================');
}
return template;
}

function override(h) {
var c = h.compile,
pc = h.precompile,
privateFilters = xssFilters._privFilters;

// override precompile function to preprocess the template first
h.precompile = function (template, options) {
return pc.call(this, preprocess(template), options);
};

// override compile function to preprocess the template first
h.compile = function (template, options) {
return c.call(this, preprocess(template), options);
};

// register below the filters that are automatically applied by context parser
[
'y',
'yd', 'yc',
'yavd', 'yavs', 'yavu',
'yu', 'yuc',
'yubl', 'yufull'
].forEach(function(filterName){
h.registerHelper(filterName, privateFilters[filterName]);
});

// register below the filters that might be manually applied by developers
[
'inHTMLData', 'inHTMLComment',
'inSingleQuotedAttr', 'inDoubleQuotedAttr', 'inUnQuotedAttr',
'uriInSingleQuotedAttr', 'uriInDoubleQuotedAttr', 'uriInUnQuotedAttr', 'uriInHTMLData', 'uriInHTMLComment',
'uriPathInSingleQuotedAttr', 'uriPathInDoubleQuotedAttr', 'uriPathInUnQuotedAttr', 'uriPathInHTMLData', 'uriPathInHTMLComment',
'uriQueryInSingleQuotedAttr', 'uriQueryInDoubleQuotedAttr', 'uriQueryInUnQuotedAttr', 'uriQueryInHTMLData', 'uriQueryInHTMLComment',
'uriComponentInSingleQuotedAttr', 'uriComponentInDoubleQuotedAttr', 'uriComponentInUnQuotedAttr', 'uriComponentInHTMLData', 'uriComponentInHTMLComment',
'uriFragmentInSingleQuotedAttr', 'uriFragmentInDoubleQuotedAttr', 'uriFragmentInUnQuotedAttr', 'uriFragmentInHTMLData', 'uriFragmentInHTMLComment'
].forEach(function(filterName){
h.registerHelper(filterName, xssFilters[filterName]);
});
return h;
}


if (module && module.exports) {
module.exports = override(Handlebars.create());
} else {
override(Handlebars);
}
43 changes: 26 additions & 17 deletions tests/unit/run-express-secure-handlebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ Authors: Nera Liu <[email protected]>
require("mocha");
var expect = require('expect.js'),
expressHandlebars = require('express-handlebars'),
expressSecureHandlebars = require('../../src/express-secure-handlebars.js');
expressSecureHandlebars = require('../../src/express-secure-handlebars.js'),
handlebars = require('handlebars');

describe("Express Secure Handlebars test suite", function() {

it("Express Secure Handlebars same signature test", function() {
it("same signature test", function() {
// console.log(expressHandlebars);
expect(typeof expressHandlebars).to.be.equal('function');
expect(typeof expressHandlebars.create).to.be.equal('function');
expect(typeof expressHandlebars.ExpressHandlebars).to.be.equal('function');
expect(expressHandlebars.create).to.be.ok();
expect(expressHandlebars.ExpressHandlebars).to.be.ok();
expect(expressHandlebars.create().compileTemplate).to.be.ok();


// console.log(expressSecureHandlebars);
Expand All @@ -32,41 +32,50 @@ Authors: Nera Liu <[email protected]>
expect(typeof expressSecureHandlebars.ExpressHandlebars).to.be.equal('function');
expect(expressSecureHandlebars.create).to.be.ok();
expect(expressSecureHandlebars.ExpressHandlebars).to.be.ok();
expect(expressSecureHandlebars.create().compileTemplate).to.be.ok();
});

it("Express Secure Handlebars new instance test", function() {
it("new instance test", function() {
var expHbs = new expressHandlebars();
expect(typeof expHbs).to.be.equal('function');
var expSecureHbs = new expressSecureHandlebars();
expect(typeof expSecureHbs).to.be.equal('function');
});

it("Express Secure Handlebars create() new instance with handlebars test", function() {
it("create() new instance with handlebars test", function() {
var expHbs = expressHandlebars.create();
expect(expHbs.handlebars).to.be.ok();
var expSecureHbs = expressSecureHandlebars.create();
expect(expSecureHbs.handlebars).to.be.ok();
});

var data = {url: 'javascript:alert(1)'};
it("Express Secure Handlebars fallback on error test", function() {
var template = '{{#if url}}<a href="{{url}}"{{else}}<a href="{{url}}">closed</a>{{/if}}';
var expSecureHbs = expressSecureHandlebars.create();
var t1 = expSecureHbs.compileTemplate(template);

var expHbs = expressHandlebars.create();
var t2 = expHbs.compileTemplate(template);
it("empty template test", function() {
expect(new expressSecureHandlebars.create().handlebars.compile('')(data)).to.be.equal('');
});

var template = '{{#if url}}<a href="{{url}}"{{else}}<a href="{{url}}">closed</a>{{/if}}';
it("handlebars fallback on compile error test", function() {
var t1 = expressSecureHandlebars.create().handlebars.compile(template);
var t2 = expressHandlebars.create().handlebars.compile(template);

expect(t1(data)).to.be.equal(t2(data));
});

it("Express Secure Handlebars compileTemplate test", function() {
it("handlebars fallback on precompile error test", function() {
var templateSpec1 = expressSecureHandlebars.create().handlebars.precompile(template);
var templateSpec2 = expressHandlebars.create().handlebars.precompile(template);
var t1 = handlebars.template(eval('(' + templateSpec1 + ')'));
var t2 = handlebars.template(eval('(' + templateSpec2 + ')'));

expect(t1(data)).to.be.equal(t2(data));
});

it("handlebars compile test", function() {
var template = '<a href="{{url}}">closed</a>';
var expSecureHbs = expressSecureHandlebars.create();
var t1 = expSecureHbs.compileTemplate(template);
var t1 = expressSecureHandlebars.create().handlebars.compile(template);
var t2 = expressHandlebars.create().handlebars.compile(template);

var expHbs = expressHandlebars.create();
var t2 = expHbs.compileTemplate(template);
expect(t1(data)).not.to.be.equal(t2(data));
});
});
Expand Down