Skip to content
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
2 changes: 1 addition & 1 deletion demo/esm/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const engine = new Liquid({
// layout files for `{% layout %}`
layouts: process.cwd() + '/layouts',
// partial files for `{% include %}` and `{% render %}`
partials: process.cwd() + '/partials'
partials: [process.cwd() + '/partials', 'node_modules']
})

const ctx = {
Expand Down
2 changes: 1 addition & 1 deletion demo/esm/test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
set -ex
set -e

npm start | grep 'LiquidJS Demo'
2 changes: 1 addition & 1 deletion demo/express/test.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set -x
set -e

LOG_FILE=$(mktemp)
npm start > $LOG_FILE 2>&1 &
Expand Down
2 changes: 1 addition & 1 deletion demo/nodejs/test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
set -ex
set -e

npm start | grep 'NodeJS Demo for LiquidJS'
2 changes: 1 addition & 1 deletion demo/template/test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
set -ex
set -e

npm start | grep '\[11:8] {{ todo }}'
2 changes: 1 addition & 1 deletion demo/typescript/test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
set -ex
set -e

npm run build && npm start | grep 'TypeScript Demo for LiquidJS'
2 changes: 1 addition & 1 deletion demo/webpack/test.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set -ex
set -e

npm run build
npm start | grep 'Webpack Demo for LiquidJS'
20 changes: 7 additions & 13 deletions docs/source/tutorials/render-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,20 @@ It can be a string-typed path (see above example), or a list of root directories

```javascript
var engine = new Liquid({
root: ['views/', 'views/partials/'],
root: ['views/'],
partials: ['views/partials/'],
layouts: ['views/layouts/'],
extname: '.liquid'
});
```

{% note tip Relative Paths %}Relative paths in <code>root</code> will be resolved against <code>cwd()</code>.{% endnote %}

When `{% raw %}{% render "foo" %}{% endraw %}` is rendered or `liquid.renderFile('foo')` is called, the following files will be looked up and the first existing file will be used:
- When `parse()`, `render()` functions are called, for example `liquid.renderFile('foo')`, templates under `root` will be looked up.
- When a partial is requested, for example `{% raw %}{% render "foo" %}{% endraw %}`, templates under `partials` will be looked up.
- When a layout is requested, for example `{% raw %}{% layout "foo" %}{% endraw %}`, templates under `layouts` will be looked up.

- `cwd()`/views/foo.liquid
- `cwd()`/views/partials/foo.liquid

If none of the above files exists, an `ENOENT` error will be thrown. Here's a demo for Node.js: [demo/nodejs](https://github.com/harttle/liquidjs/tree/master/demo/nodejs).

When LiquidJS is used in browser, say current location is <https://example.com/bar/index.html>, only the first `root` will be used and the file to be fetched is:

- <https://example.com/bar/foo.liquid>

If fetch fails, a 404/500 error or network failures for example, an `ENOENT` error will be thrown.
Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/tree/master/demo/browser).
When LiquidJS is used in browser, the paths will be resolved based on current location. Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/tree/master/demo/browser).

## Abstract File System

Expand Down
23 changes: 12 additions & 11 deletions src/fs/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,26 @@ export class Loader {

public * candidates (file: string, dirs: string[], currentFile?: string, enforceRoot?: boolean) {
const { fs, extname } = this.options
if (this.shouldLoadRelative(file) && currentFile) {
const referenced = fs.resolve(this.dirname(currentFile), file, extname)
const isAllowed = (filepath: string) => {
if (!enforceRoot) return true
for (const dir of dirs) {
if (!enforceRoot || this.contains(dir, referenced)) {
// the relatively referenced file is within one of root dirs
yield referenced
break
}
if (this.contains(dir, filepath)) return true
}
return false
}

if (this.shouldLoadRelative(file) && currentFile) {
const referenced = fs.resolve(this.dirname(currentFile), file, extname)
if (isAllowed(referenced)) yield referenced
}
for (const dir of dirs) {
const referenced = fs.resolve(dir, file, extname)
if (!enforceRoot || this.contains(dir, referenced)) {
yield referenced
}
if (isAllowed(referenced)) yield referenced
}

if (fs.fallback !== undefined) {
const filepath = fs.fallback(file)
if (filepath !== undefined) yield filepath
if (filepath !== undefined && isAllowed(filepath)) yield filepath
}
}

Expand Down
20 changes: 20 additions & 0 deletions test/e2e/issues.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync, LiquidError, IfTag } from '../..'
import { spawnSync } from 'child_process'
import { resolve as resolvePath } from 'path'
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid

describe('Issues', function () {
Expand Down Expand Up @@ -173,6 +175,24 @@ describe('Issues', function () {
const html = await engine.render(tpl, { my_variable: 'foo' })
expect(html).toBe('CONTENT for /tmp/prefix/foo-bar/suffix')
})
it('should prevent path traversal in dynamic include with restricted root, #851', () => {
const projectRoot = resolvePath(__dirname, '../..')
const poc = `
const { Liquid } = require('./dist/liquid.node.js');
const e = new Liquid({ root: ['/tmp'], partials: ['/tmp'], dynamicPartials: true });
e.parseAndRender('{% include page %}', { page: '../../../etc/passwd' })
.then(() => { console.log('OK'); })
.catch(err => { console.error('ERR:' + err.message); process.exit(1); });
`
const result = spawnSync(
process.execPath,
['-e', poc],
{ cwd: projectRoot, encoding: 'utf8' }
)

expect(result.status).not.toBe(0)
expect(result.stderr).toContain('Failed to lookup')
})
it('Implement liquid/echo tags #428', () => {
const template = `{%- liquid
for value in array
Expand Down
3 changes: 1 addition & 2 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"downlevelIteration": true,
"strict": true,
"suppressImplicitAnyIndexErrors": true
"strict": true
},
"all": true
}
Loading