feat: ownPropertyOnly option to protect prototype, #454

This commit is contained in:
Harttle
2022-01-29 01:22:34 +08:00
committed by Harttle
parent 527858fc14
commit 7e99efc513
7 changed files with 93 additions and 9 deletions
+5
View File
@@ -194,4 +194,9 @@ describe('Issues', function () {
const html = engine.parseAndRenderSync(template, { array: [1, 2, 3] })
expect(html).to.equal('4#8#12#6')
})
it('#454 leaking JS prototype getter functions in evaluation', async () => {
const engine = new Liquid({ ownPropertyOnly: true })
const html = engine.parseAndRenderSync('{{foo | size}}-{{bar.coo}}', { foo: 'foo', bar: Object.create({ coo: 'COO' }) })
expect(html).to.equal('3-')
})
})
+64 -2
View File
@@ -95,11 +95,11 @@ describe('Context', function () {
})
it('should throw when deep variable not exist', async function () {
ctx.push({ foo: 'FOO' })
return expect(() => ctx.get(['foo', 'bar', 'not', 'defined'])).to.throw(/undefined variable: bar/)
return expect(() => ctx.get(['foo', 'bar', 'not', 'defined'])).to.throw(/undefined variable: foo.bar/)
})
it('should throw when itself not defined', async function () {
ctx.push({ foo: 'FOO' })
return expect(() => ctx.get(['foo', 'BAR'])).to.throw(/undefined variable: BAR/)
return expect(() => ctx.get(['foo', 'BAR'])).to.throw(/undefined variable: foo.BAR/)
})
it('should find variable in parent scope', async function () {
ctx.push({ 'foo': 'foo' })
@@ -110,6 +110,68 @@ describe('Context', function () {
})
})
describe('ownPropertyOnly', async function () {
let ctx: Context
beforeEach(function () {
ctx = new Context(ctx, {
ownPropertyOnly: true
} as any)
})
it('should return undefined for prototype object property', function () {
ctx.push({ foo: Object.create({ bar: 'BAR' }) })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
})
it('should return undefined for Array.prototype.reduce', function () {
ctx.push({ foo: [] })
return expect(ctx.get(['foo', 'reduce'])).to.equal(undefined)
})
it('should return undefined for function prototype property', function () {
function Foo () {}
Foo.prototype.bar = 'BAR'
ctx.push({ foo: new (Foo as any)() })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
})
it('should allow function constructor properties', function () {
function Foo (this: any) { this.bar = 'BAR' }
ctx.push({ foo: new (Foo as any)() })
return expect(ctx.get(['foo', 'bar'])).to.equal('BAR')
})
it('should return undefined for class method', function () {
class Foo { bar () {} }
ctx.push({ foo: new Foo() })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
})
it('should allow class property', function () {
class Foo { bar = 'BAR' }
ctx.push({ foo: new Foo() })
return expect(ctx.get(['foo', 'bar'])).to.equal('BAR')
})
it('should allow Array.prototype.length', function () {
ctx.push({ foo: [1, 2] })
return expect(ctx.get(['foo', 'length'])).to.equal(2)
})
it('should allow size to access Array.prototype.length', function () {
ctx.push({ foo: [1, 2] })
return expect(ctx.get(['foo', 'size'])).to.equal(2)
})
it('should allow size to access Set.prototype.size', function () {
ctx.push({ foo: new Set([1, 2]) })
return expect(ctx.get(['foo', 'size'])).to.equal(2)
})
it('should allow size to access Object key count', function () {
ctx.push({ foo: { bar: 'BAR', coo: 'COO' } })
return expect(ctx.get(['foo', 'size'])).to.equal(2)
})
it('should throw when property is hidden and strictVariables is true', function () {
ctx = new Context(ctx, {
ownPropertyOnly: true,
strictVariables: true
} as any)
ctx.push({ foo: Object.create({ bar: 'BAR' }) })
return expect(() => ctx.get(['foo', 'bar'])).to.throw(/undefined variable: foo.bar/)
})
})
describe('.getAll()', function () {
it('should get all properties when arguments empty', async function () {
expect(ctx.getAll()).deep.equal(scope)