express-hbs 2.4.2 → 2.5.0

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/README.md CHANGED
@@ -43,6 +43,7 @@ hbs.express4({
43
43
  partialsDir: "{String/Array} [Required] Path to partials templates, one or several directories",
44
44
 
45
45
  // OPTIONAL settings
46
+ restrictLayoutsTo: "{String} Absolute path to a directory to restrict layout directive reading from",
46
47
  blockHelperName: "{String} Override 'block' helper name.",
47
48
  contentHelperName: "{String} Override 'contentFor' helper name.",
48
49
  defaultLayout: "{String} Absolute path to default layout template",
@@ -99,9 +100,9 @@ There are three ways to use a layout, listed in precedence order
99
100
 
100
101
  2. As an option to render
101
102
 
102
- ## ⚠️ This creates a potential security vulnerability:
103
+ ## ⚠️ This creates a potential security vulnerability if used without a `restrictLayoutsTo`:
103
104
 
104
- Do not use this option in conjunction with passing user submitted data to res.render e.g. `res.render('index', req.query)`. This allows users to read arbitrary files from your filesystem!
105
+ The `restrictLayoutsTo` option will restrict reading layouts to a particular directory, if you do not pass this option then do not use the `layout` option in conjunction with passing user submitted data to res.render e.g. `res.render('index', req.query)`. This allows users to read arbitrary files from your filesystem!
105
106
 
106
107
  ```js
107
108
  res.render('veggies', {
@@ -19,7 +19,8 @@ app.use(express.static(relative('public')));
19
19
  app.engine('hbs', hbs.express4({
20
20
  partialsDir: [relative('views/partials'), relative('views/partials-other')],
21
21
  layoutsDir: relative('views/layout'),
22
- defaultLayout: relative('views/layout/default.hbs')
22
+ defaultLayout: relative('views/layout/default.hbs'),
23
+ restrictLayoutsTo: relative('views/layout')
23
24
  }));
24
25
  app.set('view engine', 'hbs');
25
26
  app.set('views', relative('views'));
package/example/app.js CHANGED
@@ -20,7 +20,8 @@ function create(hbs, env) {
20
20
  // Hook in express-hbs and tell it where known directories reside
21
21
  app.engine('hbs', hbs.express4({
22
22
  partialsDir: [relative('views/partials'), relative('views/partials-other')],
23
- defaultLayout: relative('views/layout/default.hbs')
23
+ defaultLayout: relative('views/layout/default.hbs'),
24
+ restrictLayoutsTo: viewsDir
24
25
  }));
25
26
  app.set('view engine', 'hbs');
26
27
  app.set('views', viewsDir);
package/lib/hbs.js CHANGED
@@ -99,6 +99,13 @@ ExpressHbs.prototype.declaredLayoutFile = function(str, filename) {
99
99
  ExpressHbs.prototype.cacheLayout = function(layoutFile, useCache, cb) {
100
100
  var self = this;
101
101
 
102
+ if (this.restrictLayoutsTo) {
103
+ if (!layoutFile.startsWith(this.restrictLayoutsTo)) {
104
+ var err = new Error('Cannot read ' + layoutFile + ' it does not reside in ' + this.restrictLayoutsTo);
105
+ return cb(err, null);
106
+ }
107
+ }
108
+
102
109
  // assume hbs extension
103
110
  if (path.extname(layoutFile) === '') layoutFile += this._options.extname;
104
111
 
@@ -254,6 +261,8 @@ ExpressHbs.prototype.express3 = function(options) {
254
261
  // Absolute path to the layouts directory
255
262
  this.layoutsDir = this._options.layoutsDir;
256
263
 
264
+ this.restrictLayoutsTo = this._options.restrictLayoutsTo;
265
+
257
266
  // express passes this through ___express func, gulp pass in an option
258
267
  this.viewsDir = null;
259
268
  this.viewsDirOpt = this._options.viewsDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-hbs",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "Express handlebars template engine complete with multiple layouts, partials and blocks.",
5
5
  "keywords": [
6
6
  "express4",
@@ -30,7 +30,7 @@
30
30
  "license": "MIT",
31
31
  "devDependencies": {
32
32
  "cookie-parser": "1.4.6",
33
- "eslint": "8.48.0",
33
+ "eslint": "8.54.0",
34
34
  "express": "4.18.2",
35
35
  "i18n": "0.15.1",
36
36
  "mocha": "10.2.0",
@@ -65,7 +65,8 @@ function create(hbs, env) {
65
65
 
66
66
  // Hook in express-hbs and tell it where known directories reside
67
67
  app.engine('hbs', hbs.express4({
68
- defaultLayout: path.join(viewsDir, "layout.hbs")
68
+ defaultLayout: path.join(viewsDir, 'layout.hbs'),
69
+ restrictLayoutsTo: viewsDir
69
70
  }));
70
71
  app.set('view engine', 'hbs');
71
72
  app.set('views', viewsDir);
@@ -31,7 +31,8 @@ function create(hbs, env) {
31
31
 
32
32
  // Hook in express-hbs and tell it where known directories reside
33
33
  app.engine('hbs', hbs.express3({
34
- i18n: i18n
34
+ i18n: i18n,
35
+ restrictLayoutsTo: viewsDir
35
36
  }));
36
37
  app.set('view engine', 'hbs');
37
38
  app.set('views', viewsDir);
@@ -11,7 +11,8 @@ describe('custom extension for partials view', function() {
11
11
  var dirname = path.join(__dirname, 'views/customExtension');
12
12
  var render = hbs.create().express4({
13
13
  extname: '.server.view.html',
14
- partialsDir: dirname + '/partialsDir'
14
+ partialsDir: dirname + '/partialsDir',
15
+ restrictLayoutsTo: dirname
15
16
  });
16
17
 
17
18
  it('should allow rendering multiple partials with custom extension', function(done) {
@@ -15,7 +15,8 @@ describe('helpers', function() {
15
15
  var hb = hbs.create();
16
16
  hb.registerHelper("sync", sync);
17
17
  var render = hb.express3({
18
- viewsDir: dirname
18
+ viewsDir: dirname,
19
+ restrictLayoutsTo: dirname
19
20
  });
20
21
  var locals = H.createLocals('express3', dirname);
21
22
 
@@ -38,7 +39,8 @@ describe('helpers', function() {
38
39
  hb.registerAsyncHelper("async", async);
39
40
 
40
41
  var render = hb.express3({
41
- viewsDir: dirname
42
+ viewsDir: dirname,
43
+ restrictLayoutsTo: dirname
42
44
  });
43
45
  var locals = H.createLocals('express3', dirname);
44
46
 
package/test/issues.js CHANGED
@@ -9,7 +9,9 @@ describe('issue-22 template', function() {
9
9
  var dirname = path.join(__dirname, 'issues/22');
10
10
 
11
11
  it('should use multiple layouts with caching', function(done) {
12
- var render = hbs.create().express3({});
12
+ var render = hbs.create().express3({
13
+ restrictLayoutsTo: dirname
14
+ });
13
15
  var locals1 = H.createLocals('express3', dirname, { layout: 'layout1', cache: true });
14
16
  var locals2 = H.createLocals('express3', dirname, { layout: 'layout2', cache: true });
15
17
 
@@ -30,7 +32,8 @@ describe('issue-23', function() {
30
32
 
31
33
  it('should not pass an empty or missing partial to handlebars', function(done) {
32
34
  var render = hbs.create().express3({
33
- partialsDir: [dirname + '/partials']
35
+ partialsDir: [dirname + '/partials'],
36
+ restrictLayoutsTo: dirname
34
37
  });
35
38
 
36
39
  function check(err, html) {
@@ -43,7 +46,8 @@ describe('issue-23', function() {
43
46
 
44
47
  it('should handle empty string', function(done) {
45
48
  var render = hbs.create().express3({
46
- partialsDir: [dirname + '/partials']
49
+ partialsDir: [dirname + '/partials'],
50
+ restrictLayoutsTo: dirname
47
51
  });
48
52
 
49
53
  function check(err, html) {
@@ -58,7 +62,8 @@ describe('issue-23', function() {
58
62
  it('should register empty partial', function(done) {
59
63
  var hb = hbs.create();
60
64
  var render = hb.express3({
61
- partialsDir: [dirname + '/partials']
65
+ partialsDir: [dirname + '/partials'],
66
+ restrictLayoutsTo: dirname
62
67
  });
63
68
  hb.handlebars.registerPartial('emptyPartial', '');
64
69
 
@@ -82,7 +87,8 @@ describe('issue-23', function() {
82
87
  it('should register partial that results in empty string (comment)', function(done) {
83
88
  var hb = hbs.create();
84
89
  var render = hb.express3({
85
- partialsDir: [dirname + '/partials']
90
+ partialsDir: [dirname + '/partials'],
91
+ restrictLayoutsTo: dirname
86
92
  });
87
93
  // this fails
88
94
  //hb.handlebars.registerPartial('emptyComment', '{{! just a comment}}');
@@ -110,7 +116,8 @@ describe('issue-23', function() {
110
116
  describe('issue-21', function() {
111
117
  var dirname = path.join(__dirname, 'issues/21');
112
118
  var render = hbs.create().express3({
113
- layoutsDir: dirname + '/views/layouts'
119
+ layoutsDir: dirname + '/views/layouts',
120
+ restrictLayoutsTo: dirname
114
121
  });
115
122
 
116
123
  it('should allow specifying layouts without the parent dir', function(done) {
@@ -158,7 +165,9 @@ describe('issue-21', function() {
158
165
 
159
166
  it('should treat layouts relative to views directory if layoutsDir is not passed', function(done) {
160
167
  var dirname = path.join(__dirname, 'issues/21');
161
- var render = hbs.create().express3();
168
+ var render = hbs.create().express3({
169
+ restrictLayoutsTo: dirname
170
+ });
162
171
 
163
172
  function check(err, html) {
164
173
  assert.ifError(err);
@@ -177,7 +186,9 @@ describe('issue-49', function() {
177
186
 
178
187
  it('should report filename with error', function(done) {
179
188
  var hb = hbs.create()
180
- var render = hb.express3({});
189
+ var render = hb.express3({
190
+ restrictLayoutsTo: dirname
191
+ });
181
192
  var locals = H.createLocals('express3', dirname, {});
182
193
  render(dirname + '/error.hbs', locals, function(err, html) {
183
194
  assert(err.stack.indexOf('[error.hbs]') > 0);
@@ -187,7 +198,9 @@ describe('issue-49', function() {
187
198
 
188
199
  it('should report relative filename with error', function(done) {
189
200
  var hb = hbs.create()
190
- var render = hb.express3({});
201
+ var render = hb.express3({
202
+ restrictLayoutsTo: dirname
203
+ });
191
204
  var locals = H.createLocals('express3', dirname, {});
192
205
  render(dirname + '/front/error.hbs', locals, function(err, html) {
193
206
  assert(err.stack.indexOf('[front/error.hbs]') > 0);
@@ -198,7 +211,8 @@ describe('issue-49', function() {
198
211
  it('should report filename with partial error', function(done) {
199
212
  var hb = hbs.create()
200
213
  var render = hb.express3({
201
- partialsDir: dirname + '/partials'
214
+ partialsDir: dirname + '/partials',
215
+ restrictLayoutsTo: dirname
202
216
  });
203
217
  var locals = H.createLocals('express3', dirname, {});
204
218
  render(dirname + '/partial.hbs', locals, function(err, html) {
@@ -210,7 +224,8 @@ describe('issue-49', function() {
210
224
  it('should report filename with layout error', function(done) {
211
225
  var hb = hbs.create()
212
226
  var render = hb.express3({
213
- partialsDir: dirname + '/partials'
227
+ partialsDir: dirname + '/partials',
228
+ restrictLayoutsTo: dirname
214
229
  });
215
230
  var locals = H.createLocals('express3', dirname, {});
216
231
  render(dirname + '/index.hbs', locals, function(err, html) {
@@ -231,7 +246,9 @@ describe('issue-53', function() {
231
246
  resultcb(++res);
232
247
  }, 1)
233
248
  });
234
- var render = hb.express3({});
249
+ var render = hb.express3({
250
+ restrictLayoutsTo: dirname
251
+ });
235
252
  var locals = H.createLocals('express3', dirname, {});
236
253
  render(dirname + '/index.hbs', locals, function(err, html) {
237
254
  assert.ifError(err);
@@ -253,7 +270,8 @@ describe('issue-59', function() {
253
270
  hb.registerAsyncHelper("async", async);
254
271
 
255
272
  var render = hb.express3({
256
- viewsDir: dirname
273
+ viewsDir: dirname,
274
+ restrictLayoutsTo: dirname
257
275
  });
258
276
  var locals = H.createLocals('express3', dirname);
259
277
 
@@ -272,7 +290,8 @@ describe('issue-59', function() {
272
290
  hb.registerAsyncHelper('async', async);
273
291
 
274
292
  var render = hb.express3({
275
- viewsDir: dirname
293
+ viewsDir: dirname,
294
+ restrictLayoutsTo: dirname
276
295
  });
277
296
  var locals = H.createLocals('express3', dirname);
278
297
 
@@ -290,6 +309,7 @@ describe('issue-73', function() {
290
309
  var render = hb.express3({
291
310
  viewsDir: dirname,
292
311
  partialsDir: dirname + '/partials',
312
+ restrictLayoutsTo: dirname,
293
313
  onCompile: function(eh, source, filename) {
294
314
  var options;
295
315
  if (filename && filename.indexOf('partials')) {
@@ -329,7 +349,8 @@ describe('issue-62', function() {
329
349
  hb.registerAsyncHelper("async", async);
330
350
 
331
351
  var render = hb.express3({
332
- viewsDir: dirname
352
+ viewsDir: dirname,
353
+ restrictLayoutsTo: dirname
333
354
  });
334
355
  var locals = H.createLocals('express3', dirname);
335
356
 
@@ -359,7 +380,8 @@ describe('issue-62', function() {
359
380
  hb.registerAsyncHelper("async", async);
360
381
 
361
382
  var render = hb.express3({
362
- viewsDir: dirname
383
+ viewsDir: dirname,
384
+ restrictLayoutsTo: dirname
363
385
  });
364
386
  var locals = H.createLocals('express3', dirname);
365
387
 
@@ -380,7 +402,8 @@ describe('issue-76', function() {
380
402
  var hb = hbs.create();
381
403
 
382
404
  var render = hb.express3({
383
- partialsDir: dirname
405
+ partialsDir: dirname,
406
+ restrictLayoutsTo: dirname
384
407
  });
385
408
 
386
409
  hb.cachePartials(function (err) {
@@ -396,7 +419,8 @@ describe('issue-84', function () {
396
419
 
397
420
  it('should render deeply nested partials', function (done) {
398
421
  var render = hbs.create().express3({
399
- partialsDir: [dirname + '/partials']
422
+ partialsDir: [dirname + '/partials'],
423
+ restrictLayoutsTo: dirname
400
424
  });
401
425
 
402
426
  function check(err, html) {
@@ -421,7 +445,9 @@ describe('issue-144', function() {
421
445
  resultcb(new hbs.SafeString('<p><code>\'$example$\'</code> abcd</p>'));
422
446
  }, 1)
423
447
  });
424
- var render = hb.express3({});
448
+ var render = hb.express3({
449
+ restrictLayoutsTo: dirname
450
+ });
425
451
  var locals = H.createLocals('express3', dirname, {});
426
452
  render(dirname + '/index.hbs', locals, function(err, html) {
427
453
  assert.equal('<div><p><code>\'$example$\'</code> abcd</p></div>\n', html);
@@ -441,7 +467,9 @@ describe('issue-153', function() {
441
467
  done();
442
468
  }
443
469
  var hb = hbs.create()
444
- var render = hb.express3({});
470
+ var render = hb.express3({
471
+ restrictLayoutsTo: dirname
472
+ });
445
473
  var locals = H.createLocals('express3', dirname, { });
446
474
  render(dirname + '/index.hbs', locals, check);
447
475
  });
@@ -1,5 +1,6 @@
1
1
  var request = require('supertest');
2
2
  var assert = require('assert');
3
+ var path = require('path');
3
4
  var hbs = require('..');
4
5
 
5
6
 
@@ -65,6 +66,7 @@ describe('layouts', function() {
65
66
 
66
67
  it ('should process template-specified layout without option', function(done) {
67
68
  var render = hbs.create().express3({
69
+ restrictLayoutsTo: dirname
68
70
  });
69
71
  var locals = createLocals('express3', dirname);
70
72
 
@@ -76,6 +78,7 @@ describe('layouts', function() {
76
78
 
77
79
  it ('should allow options.layout to be specified', function(done) {
78
80
  var render = hbs.create().express3({
81
+ restrictLayoutsTo: dirname
79
82
  });
80
83
  var locals = createLocals('express3', dirname, { layout: 'layouts/default' });
81
84
 
@@ -85,8 +88,23 @@ describe('layouts', function() {
85
88
  });
86
89
  });
87
90
 
91
+ it('should error when using a layout outside of the restrictLayoutsTo', function(done) {
92
+ var render = hbs.create().express3({
93
+ restrictLayoutsTo: path.resolve(path.join(__dirname, '../'))
94
+ });
95
+ var locals = createLocals('express3', dirname, {layout: '/Users/egg/Code/Ghost/ghost/core/package.json'});
96
+
97
+ render(dirname + '/aside.hbs', locals, function (err, html) {
98
+ if (!err) {
99
+ return done(new Error('We expect an error when reading'));
100
+ }
101
+ return done();
102
+ });
103
+ });
104
+
88
105
  it ('should not process template-specified layout when options.layout is falsy', function(done) {
89
106
  var render = hbs.create().express3({
107
+ restrictLayoutsTo: dirname
90
108
  });
91
109
  var locals = createLocals('express3', dirname, { layout: false });
92
110
 
@@ -11,7 +11,9 @@ describe('local template options', function () {
11
11
 
12
12
  it('merges res.locals._templateOptions with the self._templateOptions', function (done) {
13
13
  var instance = hbs.create();
14
- var render = instance.express3({});
14
+ var render = instance.express3({
15
+ restrictLayoutsTo: dirname
16
+ });
15
17
  instance.updateTemplateOptions({
16
18
  data: {
17
19
  greeting: 'Hello,',
@@ -37,7 +39,9 @@ describe('local template options', function () {
37
39
 
38
40
  it('removes _templateOptions from the locals data', function (done) {
39
41
  var instance = hbs.create();
40
- var render = instance.express3({});
42
+ var render = instance.express3({
43
+ restrictLayoutsTo: dirname
44
+ });
41
45
  instance.updateTemplateOptions({
42
46
  data: {
43
47
  greeting: 'Hello,',
@@ -66,7 +70,9 @@ describe('local template options', function () {
66
70
 
67
71
  it('merges res.locals._templateOptions with the self._templateOptions', function (done) {
68
72
  var instance = hbs.create();
69
- var render = instance.express4({});
73
+ var render = instance.express4({
74
+ restrictLayoutsTo: dirname
75
+ });
70
76
  instance.updateTemplateOptions({
71
77
  data: {
72
78
  greeting: 'Hello,',
@@ -92,7 +98,9 @@ describe('local template options', function () {
92
98
 
93
99
  it('removes _templateOptions from the locals data', function (done) {
94
100
  var instance = hbs.create();
95
- var render = instance.express3({});
101
+ var render = instance.express3({
102
+ restrictLayoutsTo: dirname
103
+ });
96
104
  instance.updateTemplateOptions({
97
105
  data: {
98
106
  greeting: 'Hello,',
package/test/multiple.js CHANGED
@@ -10,7 +10,9 @@ describe('multiple directories', function() {
10
10
 
11
11
  beforeEach(function() {
12
12
  app = express();
13
- app.engine('hbs', hbs.express3());
13
+ app.engine('hbs', hbs.express3({
14
+ restrictLayoutsTo: './test/views/multiple'
15
+ }));
14
16
  app.set('view engine', 'hbs');
15
17
  app.get('/test1', function (req, res) {
16
18
  res.render('test1');
@@ -9,7 +9,8 @@ describe('non-express', function() {
9
9
 
10
10
  it ('should use viewsDir options', function(done) {
11
11
  var render = hbs.create().express3({
12
- viewsDir: dirname
12
+ viewsDir: dirname,
13
+ restrictLayoutsTo: dirname
13
14
  });
14
15
  var locals = H.createLocals('express3', dirname);
15
16
 
@@ -22,7 +23,8 @@ describe('non-express', function() {
22
23
  it ('should work with layoutsDir', function(done) {
23
24
  var render = hbs.create().express3({
24
25
  viewsDir: dirname,
25
- layoutsDir: dirname + '/layouts'
26
+ layoutsDir: dirname + '/layouts',
27
+ restrictLayoutsTo: dirname
26
28
  });
27
29
  var locals = H.createLocals('express3', dirname, {layout: 'default.hbs'});
28
30
 
@@ -10,7 +10,7 @@ describe('options', function() {
10
10
 
11
11
  it('should pretty print HTML', function(done) {
12
12
  var hb = hbs.create();
13
- var render = hb.express3({beautify: true});
13
+ var render = hb.express3({beautify: true, restrictLayoutsTo: dirname});
14
14
  var locals = H.createLocals('express3', dirname, {});
15
15
 
16
16
  render(dirname + '/index.hbs', locals, function(err, html) {