feat: relativeReference for render/include/layout, #395

- `relativeReference` is enabled by default, set to `false` to disable
- Referenced files are still constrained within root/partias/layouts
- fix: relative filenames are not constrained (which allows arbitrary filesystem read)

Example Usage:

{% render "../foo/bar.html" %}

Note:

../foo/bar.html' should also be within `partials` (or `root` if `partials` not set)
This commit is contained in:
Harttle
2021-10-06 17:36:37 +08:00
parent 24a19c092a
commit a3455ebd0b
15 changed files with 160 additions and 31 deletions
+8
View File
@@ -19,6 +19,14 @@ describe('tags/include', function () {
const html = await liquid.renderFile('/current.html')
return expect(html).to.equal('barfoobar')
})
it('should support relative reference', async function () {
mock({
'/foo/bar/current.html': 'bar{% include "../coo/foo.html" %}bar',
'/foo/coo/foo.html': 'foo'
})
const html = await liquid.renderFile('/foo/bar/current.html')
return expect(html).to.equal('barfoobar')
})
it('should support template string', async function () {
mock({
'/current.html': 'bar{% include "bar/{{name}}" %}bar',
+10
View File
@@ -179,6 +179,16 @@ describe('tags/layout', function () {
return expect(html).to.equal('blackredA')
})
it('should support relative reference', async function () {
mock({
'/foo/bar/parent.html': '{{color}}{%block%}{%endblock%}',
'/foo/bar/main.html': '{% layout ./parent.html color:"black"%}{%block%}A{%endblock%}'
})
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/foo/bar/main.html')
return expect(html).to.equal('blackA')
})
describe('static partial', function () {
it('should support filename with extension', async function () {
mock({
+26 -1
View File
@@ -26,7 +26,7 @@ describe('tags/render', function () {
'/current.html': 'bar{% render "foo.html" %}bar',
'/partials/foo.html': 'foo'
})
const liquid = new Liquid({ partials: '/partials' })
const liquid = new Liquid({ partials: '/partials', root: '/' })
const html = await liquid.renderFile('/current.html')
expect(html).to.equal('barfoobar')
})
@@ -223,6 +223,31 @@ describe('tags/render', function () {
const html = await liquid.renderFile('personInfo.html', ctx)
expect(html).to.equal('This is a person <p>Joe Shmoe<br/>City: Dallas</p>')
})
it('should support relative reference', async function () {
mock({
'/foo/coo/parent.html': 'X{% render ../bar/child.html, color:"red" %}Y',
'/foo/bar/child.html': 'child with {{color}}'
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo' })
const html = await staticLiquid.renderFile('coo/parent.html')
expect(html).to.equal('Xchild with redY')
})
it('should disable relative reference if specified', () => {
mock({
'/foo/coo/parent.html': 'X{% render ../bar/child.html, color:"red" %}Y',
'/foo/bar/child.html': 'child with {{color}}'
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo', relativeReference: false })
return expect(staticLiquid.renderFile('coo/parent.html')).to.be.rejectedWith(/Failed to lookup/)
})
it('should throw not found if relative reference out of root', () => {
mock({
'/foo/parent.html': 'X{% render ../bar/child.html, color:"red" %}Y',
'/bar/child.html': 'child with {{color}}'
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo', partials: '/foo' })
return expect(staticLiquid.renderFile('parent.html')).to.be.rejectedWith(/Failed to lookup "..\/bar\/child.html"/)
})
describe('static partial', function () {
it('should support filename with extention', async function () {
+18
View File
@@ -174,5 +174,23 @@ describe('LiquidOptions#cache', function () {
const y = await engine.renderFile('foo')
expect(y).to.equal('foo')
})
it('should cache relative referenced files properly', async function () {
const engine = new Liquid({
root: '/root/',
extname: '.html',
cache: true
})
mock({
'/root/foo.html': '{% render "./bar" %}',
'/root/bar.html': 'bar1',
'/root/another/foo.html': '{% render "./bar" %}',
'/root/another/bar.html': 'bar2'
})
const foo1 = await engine.renderFile('foo')
expect(foo1).to.equal('bar1')
const foo2 = await engine.renderFile('another/foo')
expect(foo2).to.equal('bar2')
})
})
})
+16
View File
@@ -0,0 +1,16 @@
import { expect, use } from 'chai'
import * as fs from '../../../src/fs/node'
import * as chaiAsPromised from 'chai-as-promised'
import { Loader } from '../../../src/fs/loader'
use(chaiAsPromised)
describe('fs/loader', function () {
describe('.candidates()', function () {
it('should break once found', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('./foo/bar', ['/root', '/root/foo'], '/root/current')]
expect(candidates.join()).to.equal('/root/foo/bar')
})
})
})
+1 -1
View File
@@ -5,7 +5,7 @@ import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('fs', function () {
describe('fs/node', function () {
describe('.resolve()', function () {
it('should resolve based on root', async function () {
const filepath = fs.resolve('/foo', 'bar.html', '.liquid')