mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
* feat: remove memoryLimit option (#910) Co-authored-by: Cursor <[email protected]> * feat: add templateLimit, outputLengthLimit, and maxDepth DoS limits Enforce v11 resource guards in render and tags, fix for offset/else behavior, and update tutorials for Tag-class registration. Co-authored-by: Cursor <[email protected]> * docs: revert unnecessary tutorial churn from memoryLimit PR Restore the two-example register-filters-tags structure (Value + Hash) and undo unrelated constructor/emitter doc edits not required for DoS limits. Co-authored-by: Cursor <[email protected]> * docs: trim security-model prose and update render-tag-content Remove diary-style engine comparisons from security-model.md. Update render-tag-content tutorial to Tag class examples with tpls class field. Co-authored-by: Cursor <[email protected]> * docs: note maxDepth stack overflow applies to renderSync only Explain why async render does not need maxDepth for stack protection based on generator/toPromise driving. Co-authored-by: Cursor <[email protected]> * refactor: track maxDepth via depthLimit Limiter on Context Replace increaseDepth/decreaseDepth with a shared Limiter that supports paired use/release, matching templateLimit and outputLengthLimit patterns. Co-authored-by: Cursor <[email protected]> * fix: remove spurious diff noise in filter files Restore misc.ts from origin/next with LF line endings and re-apply only memoryLimit removal, avoiding CRLF and blank-line churn in the export block. Co-authored-by: Cursor <[email protected]> * refactor: minimize PR diff noise Co-authored-by: Cursor <[email protected]> * feat: cap strftime pad width at 1M docs: restructure security model with production guidance Co-authored-by: Cursor <[email protected]> * refactor: simplify depthLimit in partial tags and tighten security docs Drop try/finally around depthLimit in include, layout, and render; release at generator end. Consolidate production guidance in security-model.md. Fix padded-blocks lint in dos.spec.ts. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
import { QuotedToken, RangeToken, OperatorToken, Token, PropertyAccessToken, OperatorType, operatorTypes } from '../tokens'
|
|
import { isRangeToken, isPropertyAccessToken, UndefinedVariableError, range, isOperatorToken, assert } from '../util'
|
|
import type { Context } from '../context'
|
|
import type { UnaryOperatorHandler } from '../render'
|
|
import { Drop } from '../drop'
|
|
|
|
export class Expression {
|
|
readonly postfix: Token[]
|
|
|
|
public constructor (tokens: IterableIterator<Token>) {
|
|
this.postfix = [...toPostfix(tokens)]
|
|
}
|
|
public * evaluate (ctx: Context, lenient?: boolean): Generator<unknown, unknown, unknown> {
|
|
assert(ctx, 'unable to evaluate: context not defined')
|
|
const operands: any[] = []
|
|
for (const token of this.postfix) {
|
|
if (isOperatorToken(token)) {
|
|
const r = operands.pop()
|
|
let result
|
|
if (operatorTypes[token.operator] === OperatorType.Unary) {
|
|
result = yield (ctx.opts.operators[token.operator] as UnaryOperatorHandler)(r, ctx)
|
|
} else {
|
|
const l = operands.pop()
|
|
result = yield ctx.opts.operators[token.operator](l, r, ctx)
|
|
}
|
|
operands.push(result)
|
|
} else {
|
|
operands.push(yield evalToken(token, ctx, lenient))
|
|
}
|
|
}
|
|
return operands[0]
|
|
}
|
|
public valid () {
|
|
return !!this.postfix.length
|
|
}
|
|
}
|
|
|
|
export function * evalToken (token: Token | undefined, ctx: Context, lenient = false): IterableIterator<unknown> {
|
|
if (!token) return
|
|
if ('content' in token) return token.content
|
|
if (isPropertyAccessToken(token)) return yield evalPropertyAccessToken(token, ctx, lenient)
|
|
if (isRangeToken(token)) return yield evalRangeToken(token, ctx)
|
|
}
|
|
|
|
function * evalPropertyAccessToken (token: PropertyAccessToken, ctx: Context, lenient: boolean): IterableIterator<unknown> {
|
|
const props: (string | number | Drop)[] = []
|
|
for (const prop of token.props) {
|
|
props.push((yield evalToken(prop, ctx, false)) as unknown as string | number | Drop)
|
|
}
|
|
try {
|
|
if (token.variable) {
|
|
const variable = yield evalToken(token.variable, ctx, lenient)
|
|
return yield ctx._getFromScope(variable, props)
|
|
} else {
|
|
return yield ctx._get(props)
|
|
}
|
|
} catch (e) {
|
|
if (lenient && (e as Error).name === 'InternalUndefinedVariableError') return null
|
|
throw (new UndefinedVariableError(e as Error, token))
|
|
}
|
|
}
|
|
|
|
export function evalQuotedToken (token: QuotedToken) {
|
|
return token.content
|
|
}
|
|
|
|
function * evalRangeToken (token: RangeToken, ctx: Context) {
|
|
const low: number = yield evalToken(token.lhs, ctx)
|
|
const high: number = yield evalToken(token.rhs, ctx)
|
|
return range(+low, +high + 1)
|
|
}
|
|
|
|
function * toPostfix (tokens: IterableIterator<Token>): IterableIterator<Token> {
|
|
const ops: OperatorToken[] = []
|
|
for (const token of tokens) {
|
|
if (isOperatorToken(token)) {
|
|
while (ops.length && ops[ops.length - 1].getPrecedence() > token.getPrecedence()) {
|
|
yield ops.pop()!
|
|
}
|
|
ops.push(token)
|
|
} else yield token
|
|
}
|
|
while (ops.length) {
|
|
yield ops.pop()!
|
|
}
|
|
}
|