express-secure-handlebars
Advanced tools
Comparing version 2.0.4 to 2.1.0
{ | ||
"name": "express-secure-handlebars", | ||
"version": "2.0.4", | ||
"version": "2.1.0", | ||
"licenses": [ | ||
@@ -41,2 +41,4 @@ { | ||
"handlebars": "^3.0.3", | ||
"object.assign": "^3.0.1", | ||
"promise": "^7.0.3", | ||
"secure-handlebars": "^1.1.1", | ||
@@ -53,2 +55,4 @@ "xss-filters": "^1.2.4" | ||
"grunt-mocha-istanbul": "^2.3.0", | ||
"mocha": "^2.3.4", | ||
"istanbul": "^0.4.0", | ||
"supertest": "^0.15.0" | ||
@@ -55,0 +59,0 @@ }, |
@@ -12,4 +12,7 @@ /* | ||
var util = require("util"), | ||
expressHandlebars = require('express-handlebars').ExpressHandlebars, | ||
secureHandlebars = require('secure-handlebars'); | ||
ExpressHandlebars = require('express-handlebars').ExpressHandlebars, | ||
secureHandlebars = require('secure-handlebars'), | ||
assign = Object.assign || require('object.assign'), | ||
Promise = require('promise'), | ||
path = require('path'); | ||
@@ -24,16 +27,119 @@ function ExpressSecureHandlebars(config) { | ||
this.constructor.super_.call(this, config); | ||
// dedicate a special express-handlebars for fetching raw partials | ||
this._exphbsForRawPartials = new ExpressHandlebarsForRawPartials(config); | ||
this._partialsCache = { | ||
preprocessed: {} | ||
}; | ||
// compilerOptions is being used for passing params to secure-handlebars | ||
this.compilerOptions || (this.compilerOptions = {}); | ||
this.compilerOptions.shbsPartialsCache = this._partialsCache; | ||
} | ||
/* inheriting the express-handlebars */ | ||
util.inherits(ExpressSecureHandlebars, expressHandlebars); | ||
util.inherits(ExpressSecureHandlebars, ExpressHandlebars); | ||
/* override ExpressHandlebars.render() to expose filePath as compilerOptions */ | ||
// always returns an empty cache, since the partial is loaded and analyzed on-demand | ||
// TODO: preprocess all templates, and export all partials for compatibility | ||
ExpressSecureHandlebars.prototype.getPartials = function (options) { | ||
return {}; | ||
}; | ||
// retrieve file content directly from the preprocessed partial cache if it exists | ||
ExpressSecureHandlebars.prototype._getFile = function (filePath, options) { | ||
var file = this._partialsCache.preprocessed[path.relative('.', filePath)]; | ||
return file ? Promise.resolve(file) : ExpressHandlebars.prototype._getFile.call(this, filePath, options); | ||
}; | ||
// _getTemplates literally has the core of getTemplates, except that it takes an array of filePaths as input | ||
/* | ||
// getTemplates can be rewritten to use _getTemplates in the following way | ||
ExpressSecureHandlebars.prototype.getTemplates = function (dirPath, options) { | ||
options || (options = {}); | ||
return this._getDir(dirPath, {cache: options.cache}).map(function (filePath) { | ||
return path.join(dirPath, filePath); | ||
}).then(function(filePaths) { | ||
return this._getTemplates(filePaths, options); | ||
}.bind(this)); | ||
}; | ||
*/ | ||
ExpressSecureHandlebars.prototype._getTemplates = function (filePaths, options) { | ||
options || (options = {}); | ||
var templates = filePaths.map(function (filePath) { | ||
return this.getTemplate(filePath, options); | ||
}, this); | ||
return Promise.all(templates).then(function (templates) { | ||
return filePaths.reduce(function (hash, filePath, i) { | ||
hash[filePath] = templates[i]; | ||
return hash; | ||
}, {}); | ||
}); | ||
}; | ||
// override render() for on-demand partial preprocessing and (pre-)compilation | ||
ExpressSecureHandlebars.prototype.render = function (filePath, context, options) { | ||
options || (options = {}); | ||
// expose filePath as processingFile in compilerOptions for secure-handlebars | ||
this.compilerOptions || (this.compilerOptions = {}); | ||
this.compilerOptions.processingFile = filePath; | ||
return expressHandlebars.prototype.render.call(this, filePath, context, options); | ||
// ensure getPartials() is called to fetch raw partials | ||
return this._exphbsForRawPartials.getPartials(options).then(function(rawPartialsCache) { | ||
// in constructor, this.compilerOptions.shbsPartialsCache = this._partialsCache | ||
this._partialsCache.raw = rawPartialsCache; | ||
// this._partialsCache.preprocessed is filled by the underlying (pre-)compile() in getTemplate() | ||
// TODO: improve this by keeping the returned (pre-)compiled template | ||
return this.getTemplate(filePath, options).catch(function(err){ | ||
throw err; | ||
}); | ||
}.bind(this)).then(function() { | ||
var partialKeys = Object.keys(this._partialsCache.preprocessed); | ||
// disable preprocessing partials in partial | ||
this.compilerOptions.enablePartialProcessing = false; | ||
// Merge render-level and (pre-)compiled pre-processed partials together | ||
return Promise.all([ | ||
this._getTemplates(partialKeys, options), // (pre-)compile pre-processed partials | ||
options.partials // collected at renderView | ||
]).then(function (partials) { | ||
return assign.apply(null, [{}].concat(partials)); | ||
}).catch(function(err) { | ||
throw err; | ||
}).finally(function() { | ||
// re-enable partial handling | ||
this.compilerOptions.enablePartialProcessing = true; | ||
}.bind(this)); | ||
}.bind(this)).then(function(partials) { | ||
options.partials = partials; | ||
return ExpressHandlebars.prototype.render.call(this, filePath, context, options); | ||
}.bind(this)); | ||
}; | ||
function ExpressHandlebarsForRawPartials(config) { | ||
this.constructor.super_.call(this, config); | ||
} | ||
util.inherits(ExpressHandlebarsForRawPartials, ExpressHandlebars); | ||
ExpressHandlebarsForRawPartials.prototype._precompileTemplate = ExpressHandlebarsForRawPartials.prototype._compileTemplate = function (template, options) { | ||
return template; | ||
}; | ||
/* exporting the same signature of express-handlebars */ | ||
@@ -40,0 +146,0 @@ exports = module.exports = exphbs; |
@@ -8,4 +8,10 @@ var express = require('express'), | ||
var routesyd = require('./routes/yd'); | ||
var routesundefined = require('./routes/undefined'); | ||
var routespartial = require('./routes/partial'); | ||
var routeslooppartial = require('./routes/looppartial'); | ||
app.engine('hbs', expressHbs({ extname:'hbs' })); | ||
app.expressSecureHandlebars = expressHbs.create({ partialsDir: __dirname + '/views/partials', | ||
extname:'hbs' }); | ||
app.engine('hbs', app.expressSecureHandlebars.engine); | ||
app.set('views', path.join(__dirname, 'views')); | ||
@@ -18,2 +24,5 @@ app.set('view engine', 'hbs'); | ||
app.use('/yd', routesyd); | ||
app.use('/undefined', routesundefined); | ||
app.use('/partial', routespartial); | ||
app.use('/looppartial', routeslooppartial); | ||
app.get('/ok', function(req, res){ | ||
@@ -20,0 +29,0 @@ res.status(200).send('ok'); |
@@ -26,3 +26,2 @@ /* | ||
.get('/ok') | ||
.expect(200) | ||
.end(function(err, res) { | ||
@@ -38,3 +37,2 @@ expect(res.text).to.be.equal('ok'); | ||
.get('/') | ||
.expect(200) | ||
.end(function(err, res) { | ||
@@ -50,3 +48,2 @@ expect(res.text).to.be.equal('<h1>express secure handlebars</h1>\n'); | ||
.get('/yd') | ||
.expect(200) | ||
.end(function(err, res) { | ||
@@ -59,4 +56,59 @@ expect(res.text).to.be.equal("<div>><'\"& </div>\n"); | ||
it("Express Secure Handlebars undefined data binding test", function(done) { | ||
request(app) | ||
.get('/undefined') | ||
.end(function(err, res) { | ||
expect(res.text).to.be.match(/<div><\/div>/); | ||
expect(res.text).to.be.match(/<input id=\ufffd>/); | ||
expect(res.status).to.be.equal(200); | ||
done(); | ||
}); | ||
}); | ||
it("Express Secure Handlebars partial test", function(done) { | ||
request(app) | ||
.get('/partial') | ||
.end(function(err, res) { | ||
expect(res.text).to.be.match(/header/); | ||
expect(res.text).to.be.match(/js/); | ||
expect(res.status).to.be.equal(200); | ||
done(); | ||
}); | ||
}); | ||
it("Express Secure Handlebars partial cache test", function(done) { | ||
var cacheTest = function() { | ||
var esh = app.expressSecureHandlebars; | ||
expect(esh.compilerOptions).to.be.ok(); | ||
expect(esh.compilerOptions.shbsPartialsCache).to.be.ok(); | ||
expect(esh.compilerOptions.shbsPartialsCache.raw['header']).to.equal('{{exp}}\n\n'); | ||
expect(esh.compilerOptions.shbsPartialsCache.raw['l1']).to.equal('{{> l2 }}\n'); | ||
expect(esh.compilerOptions.shbsPartialsCache.raw['l2']).to.equal('{{> l1 }}\n'); | ||
expect(esh.compilerOptions.shbsPartialsCache.raw['script']).to.equal('{{js}}\n'); | ||
expect(esh.compilerOptions.shbsPartialsCache.preprocessed['SJST/1/header']).to.equal('{{{yd exp}}}\n\n'); | ||
expect(esh.compilerOptions.shbsPartialsCache.preprocessed['SJST/6/script']).to.equal('{{{y js}}}\n'); | ||
// TODO: compiled is available only when options.cache is true | ||
// expect(esh.compilerOptions.shbsPartialsCache.compiled['SJST/1/header']).to.be.ok(); | ||
// expect(esh.compilerOptions.shbsPartialsCache.compiled['SJST/6/script']).to.be.ok(); | ||
done(); | ||
} | ||
request(app) | ||
.get('/partial') | ||
.end(function(err, res) { | ||
cacheTest(); | ||
}); | ||
}); | ||
// make sure it won't be infinite loop | ||
it("Express Secure Handlebars loop partial test", function(done) { | ||
request(app) | ||
.get('/looppartial') | ||
.end(function(err, res) { | ||
expect(res.status).to.be.equal(500); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}()); |
@@ -21,2 +21,4 @@ /* | ||
this.timeout(5000); | ||
it("same signature test", function() { | ||
@@ -30,3 +32,2 @@ // console.log(expressHandlebars); | ||
// console.log(expressSecureHandlebars); | ||
@@ -85,12 +86,22 @@ expect(typeof expressSecureHandlebars).to.be.equal('function'); | ||
it("handlebars getTemplate test", function() { | ||
var templateFile = path.resolve("views/yd.hbs"); | ||
it("handlebars render test", function(done) { | ||
var filePath = path.resolve('../express/views/yd.hbs'); | ||
var expSecureHbs = expressSecureHandlebars.create(); | ||
expSecureHbs.render(templateFile); | ||
expect(expSecureHbs.compilerOptions).to.be.ok(); | ||
expect(expSecureHbs.compilerOptions.processingFile).to.be.ok(); | ||
expect(expSecureHbs.compilerOptions.processingFile).to.be.match(/yd\.hbs/); | ||
expect(expSecureHbs.compilerOptions.shbsPartialsCache).to.be.ok(); | ||
expSecureHbs.render(filePath, {input: '<script>alert(1)</script>'}).then(function(output){ | ||
if (output === '<div><script>alert(1)</script></div>\n' && | ||
expSecureHbs.compilerOptions.processingFile === filePath) { | ||
done(); | ||
} | ||
}); | ||
}); | ||
}); | ||
}()); |
28244
25
450
6
10
+ Addedobject.assign@^3.0.1
+ Addedpromise@^7.0.3
+ Addedasap@2.0.6(transitive)
+ Addeddefine-data-property@1.1.4(transitive)
+ Addeddefine-properties@1.2.1(transitive)
+ Addedes-define-property@1.0.0(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-intrinsic@1.2.4(transitive)
+ Addedgopd@1.0.1(transitive)
+ Addedhas-property-descriptors@1.0.2(transitive)
+ Addedhas-proto@1.0.3(transitive)
+ Addedhas-symbols@1.0.3(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedobject-keys@1.1.1(transitive)
+ Addedobject.assign@3.0.1(transitive)
+ Addedpromise@7.3.1(transitive)