mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-13 11:20:40 -07:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5359301864 | ||
|
|
4775227358 |
+5
-3
@@ -6,8 +6,10 @@ Only the latest major version is supported with security updates. It can be chan
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please contact harttleharttle@gmail.com to report a vulnerability or change request.
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
- If the vulnerability in question affects common use cases, it will be treated as a bug and fixed very soon (typically within 1 week).
|
||||
Report them via [GitHub Security Advisories — Report a vulnerability](https://github.com/harttle/liquidjs/security/advisories/new).
|
||||
|
||||
- If the vulnerability in question affects common use cases, it will be treated as a bug and fixed very soon (typically within a month).
|
||||
- Otherwise, it'll be scheduled in the same priority of feature request (which is lower than bugs).
|
||||
- If the request is declined, you'll receive a reply email anyway (most likely there will be a discussion).
|
||||
- If the request is declined, you'll receive a reply anyway (most likely there will be a discussion).
|
||||
|
||||
+1
-3
@@ -17,12 +17,10 @@ debug: false
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GM713991QQ"></script>
|
||||
<script>
|
||||
<script type="text/plain" data-category="analytics">
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-GM713991QQ');
|
||||
</script>
|
||||
+1
@@ -5,6 +5,7 @@
|
||||
{{__('footer.license')}}
|
||||
</div>
|
||||
<div id="footer-links">
|
||||
<button type="button" class="footer-link cookie-preferences" data-cc="show-consentModal" title="Manage cookie preferences"><i class="icon-shield"></i></button>
|
||||
<a href="https://twitter.com/{{ config.twitter }}" class="footer-link" target="_blank"><i class="icon-twitter"></i></a>
|
||||
<a href="https://opencollective.com/{{ config.oc }}" class="footer-link" target="_blank"><i class="icon-opencollective"></i></a>
|
||||
<a href="https://github.com/{{ config.github }}" class="footer-link" target="_blank"><i class="icon-github"></i></a>
|
||||
|
||||
+3
-8
@@ -32,12 +32,7 @@
|
||||
<meta name="msapplication-TileImage" content="{{ url_for('icon/mstile-144x144.png') }}">
|
||||
{{ css('css/navy') }}
|
||||
{{ feed_tag('atom.xml') }}
|
||||
<script src="https://cdn.cookiehub.eu/c2/e8e44c93.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') return;
|
||||
var cpm = {};
|
||||
window.cookiehub.load(cpm);
|
||||
});
|
||||
</script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/[email protected]/dist/cookieconsent.css">
|
||||
<script src="https://cdn.jsdelivr.net/gh/orestbida/[email protected]/dist/cookieconsent.umd.js"></script>
|
||||
{{ js('js/cookieconsent-config') }}
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#cc-main
|
||||
--cc-btn-primary-bg: var(--color-link)
|
||||
--cc-btn-primary-color: #fff
|
||||
--cc-btn-primary-border-color: var(--color-link)
|
||||
--cc-btn-primary-hover-bg: var(--color-link-hover)
|
||||
--cc-btn-primary-hover-border-color: var(--color-link-hover)
|
||||
--cc-btn-secondary-bg: #eaeff2
|
||||
--cc-btn-secondary-color: var(--color-default)
|
||||
--cc-btn-secondary-border-color: #eaeff2
|
||||
--cc-toggle-on-bg: var(--color-link)
|
||||
|
||||
.cc--darkmode #cc-main
|
||||
--cc-btn-secondary-bg: #3a4248
|
||||
--cc-btn-secondary-color: var(--color-default)
|
||||
--cc-btn-secondary-border-color: #3a4248
|
||||
@@ -42,6 +42,13 @@
|
||||
@media mq-normal
|
||||
font-size: 30px
|
||||
|
||||
.cookie-preferences
|
||||
background: none
|
||||
border: 0
|
||||
padding: 0
|
||||
cursor: pointer
|
||||
color: inherit
|
||||
|
||||
.icon-oc
|
||||
height: 36px
|
||||
width: 30px
|
||||
|
||||
+1
@@ -9,6 +9,7 @@
|
||||
@import "_partial/page"
|
||||
@import "_partial/mobile_nav"
|
||||
@import "_partial/footer"
|
||||
@import "_partial/cookieconsent"
|
||||
@import "_partial/highlight"
|
||||
@import "_partial/icomoon.css"
|
||||
@import "_partial/docsearch.min.css"
|
||||
@@ -0,0 +1,49 @@
|
||||
(function () {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('cc--darkmode');
|
||||
}
|
||||
|
||||
var config = {
|
||||
guiOptions: {
|
||||
consentModal: {
|
||||
equalWeightButtons: false
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
necessary: {
|
||||
enabled: true,
|
||||
readOnly: true
|
||||
},
|
||||
analytics: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
language: {
|
||||
default: 'en',
|
||||
translations: {
|
||||
en: {
|
||||
consentModal: {
|
||||
title: 'We use cookies',
|
||||
description: 'This site uses cookies for analytics and to improve your experience.',
|
||||
acceptAllBtn: 'Accept',
|
||||
acceptNecessaryBtn: 'Reject'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (/^localhost$|^127\.0\.0\.1$/i.test(location.hostname)) {
|
||||
config.cookie = { secure: false };
|
||||
}
|
||||
|
||||
function run() {
|
||||
CookieConsent.run(config);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', run);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}());
|
||||
@@ -103,7 +103,7 @@ export class Context {
|
||||
return this.scopes[0]
|
||||
}
|
||||
public spawn (scope = {}) {
|
||||
const ctx = new Context(scope, this.opts, {
|
||||
return new Context(scope, this.opts, {
|
||||
sync: this.sync,
|
||||
globals: this.globals,
|
||||
strictVariables: this.strictVariables,
|
||||
@@ -112,8 +112,6 @@ export class Context {
|
||||
renderLimit: this.renderLimit,
|
||||
memoryLimit: this.memoryLimit
|
||||
})
|
||||
ctx.setRegister('partialStack', this.getRegister('partialStack', [] as string[]))
|
||||
return ctx
|
||||
}
|
||||
private findScope (key: string | number) {
|
||||
for (let i = this.scopes.length - 1; i >= 0; i--) {
|
||||
|
||||
+10
-15
@@ -3,7 +3,7 @@ import { BlockMode, createScope, Scope } from '../context'
|
||||
import { Parser } from '../parser'
|
||||
import { Argument, Arguments, PartialScope } from '../template'
|
||||
import { isString, isValueToken } from '../util'
|
||||
import { parseFilePath, renderFilePath, ParsedFileName, pushPartialStack, popPartialStack } from './render'
|
||||
import { parseFilePath, renderFilePath, ParsedFileName } from './render'
|
||||
|
||||
export default class extends Tag {
|
||||
private file: ParsedFileName
|
||||
@@ -33,21 +33,16 @@ export default class extends Tag {
|
||||
const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string
|
||||
assert(filepath, () => `illegal file path "${filepath}"`)
|
||||
|
||||
pushPartialStack(ctx, filepath, 'include')
|
||||
const saved = ctx.saveRegister('blocks', 'blockMode')
|
||||
try {
|
||||
ctx.setRegister('blocks', {})
|
||||
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
||||
const scope = createScope((yield hash.render(ctx)) as Scope)
|
||||
if (withVar) scope[filepath] = yield evalToken(withVar, ctx)
|
||||
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this.currentFile)) as Template[]
|
||||
ctx.push(ctx.opts.jekyllInclude ? createScope({ include: scope }) : scope)
|
||||
yield renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
} finally {
|
||||
ctx.restoreRegister(saved)
|
||||
popPartialStack(ctx)
|
||||
}
|
||||
ctx.setRegister('blocks', {})
|
||||
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
||||
const scope = createScope((yield hash.render(ctx)) as Scope)
|
||||
if (withVar) scope[filepath] = yield evalToken(withVar, ctx)
|
||||
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this.currentFile)) as Template[]
|
||||
ctx.push(ctx.opts.jekyllInclude ? createScope({ include: scope }) : scope)
|
||||
yield renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
ctx.restoreRegister(saved)
|
||||
}
|
||||
|
||||
public * children (partials: boolean, sync: boolean): Generator<unknown, Template[]> {
|
||||
|
||||
+17
-35
@@ -59,32 +59,27 @@ export default class extends Tag {
|
||||
const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string
|
||||
assert(filepath, () => `illegal file path "${filepath}"`)
|
||||
|
||||
pushPartialStack(ctx, filepath, 'render')
|
||||
try {
|
||||
const childCtx = ctx.spawn()
|
||||
const scope = childCtx.bottom()
|
||||
__assign(scope, yield hash.render(ctx))
|
||||
if (this.with) {
|
||||
const { value, alias } = this.with
|
||||
scope[alias || filepath] = yield evalToken(value, ctx)
|
||||
}
|
||||
const childCtx = ctx.spawn()
|
||||
const scope = childCtx.bottom()
|
||||
__assign(scope, yield hash.render(ctx))
|
||||
if (this.with) {
|
||||
const { value, alias } = this.with
|
||||
scope[alias || filepath] = yield evalToken(value, ctx)
|
||||
}
|
||||
|
||||
if (this.forBinding) {
|
||||
const { value, alias } = this.forBinding
|
||||
const collection = toEnumerable(yield evalToken(value, ctx))
|
||||
scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias as string)
|
||||
for (const item of collection) {
|
||||
scope[alias as string] = item
|
||||
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this.currentFile)) as Template[]
|
||||
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
|
||||
scope['forloop'].next()
|
||||
}
|
||||
} else {
|
||||
if (this.forBinding) {
|
||||
const { value, alias } = this.forBinding
|
||||
const collection = toEnumerable(yield evalToken(value, ctx))
|
||||
scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias as string)
|
||||
for (const item of collection) {
|
||||
scope[alias as string] = item
|
||||
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this.currentFile)) as Template[]
|
||||
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
|
||||
scope['forloop'].next()
|
||||
}
|
||||
} finally {
|
||||
popPartialStack(ctx)
|
||||
} else {
|
||||
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this.currentFile)) as Template[]
|
||||
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,16 +173,3 @@ export function * renderFilePath (file: ParsedFileName, ctx: Context, liquid: Li
|
||||
if (Array.isArray(file)) return liquid.renderer.renderTemplates(file, ctx)
|
||||
return yield evalToken(file, ctx)
|
||||
}
|
||||
|
||||
export function pushPartialStack (ctx: Context, filepath: string, tag: 'render' | 'include') {
|
||||
const stack: string[] = ctx.getRegister('partialStack', [])
|
||||
if (ctx.renderLimit.isUnlimited() && stack.includes(filepath)) {
|
||||
throw new Error(`${tag} tag cannot be nested`)
|
||||
}
|
||||
stack.push(filepath)
|
||||
}
|
||||
|
||||
export function popPartialStack (ctx: Context) {
|
||||
const stack: string[] = ctx.getRegister('partialStack', [])
|
||||
stack.pop()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,4 @@ export class Limiter {
|
||||
assert(+count <= this.limit, this.message)
|
||||
}
|
||||
}
|
||||
isUnlimited () {
|
||||
return !Number.isFinite(this.limit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,25 +117,4 @@ describe('.parseAndRender()', function () {
|
||||
await expect(liquid.renderFile('template')).rejects.toThrow(/block tag cannot be nested/)
|
||||
})
|
||||
})
|
||||
describe('render/include: self-referential partial regression', function () {
|
||||
it('should reject self-referential {% render %} via in-memory templates (no hang / OOM)', async function () {
|
||||
const liquid = new Liquid({ templates: { self: '{% render "self" %}' } })
|
||||
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/render tag cannot be nested/)
|
||||
})
|
||||
it('should reject self-referential {% include %} (no hang / OOM)', async function () {
|
||||
let root: string
|
||||
root = mkdtempSync(join(tmpdir(), 'liquid-e2e-include-nested-'))
|
||||
try {
|
||||
writeFileSync(join(root, 'self.html'), 'A{% include "self.html" %}B')
|
||||
const liquid = new Liquid({ root, extname: '.html' })
|
||||
await expect(liquid.renderFile('self')).rejects.toThrow(/include tag cannot be nested/)
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
it('should allow self-referential {% render %} when renderLimit is finite', async function () {
|
||||
const liquid = new Liquid({ templates: { self: '{% render "self" %}' }, renderLimit: 0.01 })
|
||||
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/template render limit exceeded/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -296,36 +296,4 @@ describe('tags/include', function () {
|
||||
return expect(html).toBe('FOO-')
|
||||
})
|
||||
})
|
||||
|
||||
describe('recursion', function () {
|
||||
it('should reject self-referential {% include %} (no OOM / hang)', function () {
|
||||
mock({
|
||||
'/self.html': 'A{% include "self.html" %}B'
|
||||
})
|
||||
return expect(liquid.renderFile('/self.html')).rejects.toThrow(/include tag cannot be nested/)
|
||||
})
|
||||
it('should reject indirect {% include %} cycle (no OOM / hang)', function () {
|
||||
mock({
|
||||
'/a.html': '{% include "b.html" %}',
|
||||
'/b.html': '{% include "a.html" %}'
|
||||
})
|
||||
return expect(liquid.renderFile('/a.html')).rejects.toThrow(/include tag cannot be nested/)
|
||||
})
|
||||
it('should allow self-referential {% include %} when renderLimit is finite', function () {
|
||||
mock({
|
||||
'/self.html': 'A{% include "self.html" %}B'
|
||||
})
|
||||
const limited = new Liquid({ root: '/', renderLimit: 0.01 })
|
||||
return expect(limited.renderFile('/self.html')).rejects.toThrow(/template render limit exceeded/)
|
||||
})
|
||||
it('should allow legitimate nested {% include %} chain', async function () {
|
||||
mock({
|
||||
'/a.html': 'A{% include "b.html" %}',
|
||||
'/b.html': 'B{% include "c.html" %}',
|
||||
'/c.html': 'C'
|
||||
})
|
||||
const html = await liquid.renderFile('/a.html')
|
||||
expect(html).toBe('ABC')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -394,31 +394,4 @@ describe('tags/render', function () {
|
||||
expect(html).toBe('Xchild with redY')
|
||||
})
|
||||
})
|
||||
|
||||
describe('recursion', function () {
|
||||
it('should reject self-referential {% render %} via in-memory templates (no OOM / hang)', async function () {
|
||||
const liquid = new Liquid({ templates: { self: '{% render "self" %}' } })
|
||||
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/render tag cannot be nested/)
|
||||
})
|
||||
it('should reject indirect {% render %} cycle (no OOM / hang)', async function () {
|
||||
mock({
|
||||
'/a.html': '{% render "b.html" %}',
|
||||
'/b.html': '{% render "a.html" %}'
|
||||
})
|
||||
await expect(liquid.renderFile('/a.html')).rejects.toThrow(/render tag cannot be nested/)
|
||||
})
|
||||
it('should allow self-referential {% render %} when renderLimit is finite', async function () {
|
||||
const liquid = new Liquid({ templates: { self: '{% render "self" %}' }, renderLimit: 0.01 })
|
||||
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/template render limit exceeded/)
|
||||
})
|
||||
it('should allow legitimate nested {% render %} chain', async function () {
|
||||
mock({
|
||||
'/a.html': 'A{% render "b.html" %}',
|
||||
'/b.html': 'B{% render "c.html" %}',
|
||||
'/c.html': 'C'
|
||||
})
|
||||
const html = await liquid.renderFile('/a.html')
|
||||
expect(html).toBe('ABC')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user