diff --git a/docs/source/tutorials/options.md b/docs/source/tutorials/options.md
index 1fa675d0b4..b1c2315b32 100644
--- a/docs/source/tutorials/options.md
+++ b/docs/source/tutorials/options.md
@@ -140,6 +140,8 @@ It defaults to `false`. For example, when set to `true`, a blank string would ev
**ownPropertyOnly** hides scope variables from prototypes, useful when you're passing a not sanitized object into LiquidJS or need to hide prototypes from templates. Defaults to `true`.
+Built-in DoS limits and host isolation guidance are documented in [Security Model](./security-model.html).
+
{% note info Nonexistent Tags %}
Nonexistent tags always throw errors during parsing and this behavior cannot be customized.
{% endnote %}
diff --git a/docs/source/tutorials/render-tag-content.md b/docs/source/tutorials/render-tag-content.md
index 33195b0901..cf655207fe 100644
--- a/docs/source/tutorials/render-tag-content.md
+++ b/docs/source/tutorials/render-tag-content.md
@@ -22,7 +22,7 @@ Expected output:
```
-Firstly, [register][register-tags] a tag named `wrap` and parse the content into `this.tpls`. Here in `parse(tagToken, remainTokens)`:
+Firstly, [register][register-tags] a tag named `wrap` and parse the content into `this.tpls`. In the tag `constructor(tagToken, remainTokens, liquid)`:
- `tagToken` is current token `{%raw%}{% wrap %}{%endraw%}`, and
- `remainTokens` is an array of all tokens following `{%raw%}{% wrap %}{%endraw%}` until the end of this template file.
@@ -30,11 +30,14 @@ Firstly, [register][register-tags] a tag named `wrap` and parse the content into
Basically, what we need to do is take/`.shift()` enough tags from `remainTokens` until we get an `endwrap` token (the name can be arbitrary, but by convention it should be `endwrap`). And if there's no `endwrap` until the end of the template file, we need to throw a tag-not-closed `Error`.
```javascript
-engine.registerTag('wrap', {
- parse(tagToken, remainTokens) {
- this.tpls = []
+const { Tag } = require('liquidjs')
+
+engine.registerTag('wrap', class WrapTag extends Tag {
+ tpls = []
+ constructor(tagToken, remainTokens, liquid) {
+ super(tagToken, remainTokens, liquid)
let closed = false
- while(remainTokens.length) {
+ while (remainTokens.length) {
let token = remainTokens.shift()
// we got the end tag! stop taking tokens
if (token.name === 'endwrap') {
@@ -44,11 +47,11 @@ engine.registerTag('wrap', {
// parse token into template
// parseToken() may consume more than 1 tokens
// e.g. {% if %}...{% endif %}
- let tpl = this.liquid.parser.parseToken(token, remainTokens)
+ let tpl = liquid.parser.parseToken(token, remainTokens)
this.tpls.push(tpl)
}
if (!closed) throw new Error(`tag ${tagToken.getText()} not closed`)
- },
+ }
* render(context, emitter) {
emitter.write("
")
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
@@ -57,16 +60,17 @@ engine.registerTag('wrap', {
})
```
-`.renderTemplates()` can be async; we need `yield` to wait for it to complete. For more details on async in LiquidJS, see [Sync and Async][async]. Other parts of the `render()` method are quite straightforward. Here's a JSFiddle version:
+`.renderTemplates()` can be async; we need `yield` to wait for it to complete. For more details on async in LiquidJS, see [Sync and Async][async]. Here's a JSFiddle version:
## Using ParseStream
-When it comes to complex tags like [for][for] and [if][if], the `parse()` can be very complicated. There's a [ParseStream][ParseStream] utility to organize the `parse()` in event-based style. Following is a re-written `parse()` using `ParseStream` that does exactly the same as the example above.
+For more complex tags such as [for][for] and [if][if], constructor parsing can get unwieldy. [ParseStream][ParseStream] offers an event-based API for this. The constructor below is equivalent to the example above:
```javascript
-parse(tagToken, remainTokens) {
- this.tpls = []
- this.liquid.parser.parseStream(remainTokens)
+tpls = []
+constructor(tagToken, remainTokens, liquid) {
+ super(tagToken, remainTokens, liquid)
+ liquid.parser.parseStream(remainTokens)
.on('template', tpl => this.tpls.push(tpl))
// note that we cannot use arrow function because we need `this`
.on('tag:endwrap', function () { this.stop() })
@@ -103,15 +107,18 @@ As you've noticed, there's an additional `repeat.i` in the context of `repeat`.
Each time we enter a new *Context*, we need to push a new *Scope*. And when we finish rendering and exit the *Context*, we pop the *Scope* from the *Context*. As you can see in the following implementation:
```javascript
-engine.registerTag('repeat', {
- parse(tagToken, remainTokens) {
- this.tpls = []
- this.liquid.parser.parseStream(remainTokens)
+const { Tag } = require('liquidjs')
+
+engine.registerTag('repeat', class RepeatTag extends Tag {
+ tpls = []
+ constructor(tagToken, remainTokens, liquid) {
+ super(tagToken, remainTokens, liquid)
+ liquid.parser.parseStream(remainTokens)
.on('template', tpl => this.tpls.push(tpl))
.on('tag:endrepeat', function () { this.stop() })
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
.start()
- },
+ }
* render(context, emitter) {
const repeat = { i: 1 }
context.push({ repeat })
@@ -123,7 +130,7 @@ engine.registerTag('repeat', {
})
```
-The `parse()` is exactly the same as `wrap` tag, we repeat the content simply by calling `.renderTemplates(this.tpls)` twice during `render()`. Here's the JSFiddle:
+The constructor is the same as in the `wrap` tag; we repeat the content by calling `.renderTemplates(this.tpls)` twice during `render()`. Here's the JSFiddle:
{% note warn Use Push & Pop in Pairs %}
`context.push()` and `context.pop()` have to be used in pairs. Failing to `pop()` the *Scope* you pushed will leak the *Scope* to latter templates and may corrupt the *Context* stack.
diff --git a/docs/source/tutorials/security-model.md b/docs/source/tutorials/security-model.md
index 5d65a01967..3c6b4aa746 100644
--- a/docs/source/tutorials/security-model.md
+++ b/docs/source/tutorials/security-model.md
@@ -2,21 +2,19 @@
title: Security Model
---
-LiquidJS provides DoS-oriented limits (`parseLimit`, `renderLimit`, `memoryLimit`) to reduce risk. This page summarizes those limits, [`ownPropertyOnly`][ownPropertyOnly], custom [`Drop`][drop] usage, and the security boundary to assume in production.
+LiquidJS provides DoS-oriented limits (`parseLimit`, `templateLimit`, `outputLengthLimit`, `maxDepth`) to reduce risk. This page summarizes those limits, [`ownPropertyOnly`][ownPropertyOnly], custom [`Drop`][drop] usage, and the security boundary to assume in production.
-## Security boundary
+## At a glance
-The built-in limits are cooperative safeguards, not strict runtime isolation.
-
-- They do **not** equal process RSS/heap usage.
-- They do **not** sandbox JavaScript execution.
-- They should be combined with process/container limits and request timeouts for defense in depth.
-
-## Limits at a glance
+LiquidJS ships a thin cooperative DoS layer:
- [parseLimit][parseLimit]: limit total template size per `parse()` call.
-- [renderLimit][renderLimit]: limit total render time per `render()` call.
-- [memoryLimit][memoryLimit]: cooperatively limit memory-sensitive allocations counted by LiquidJS.
+- [templateLimit][templateLimit]: limit total tag/HTML/output nodes rendered per `render()` call.
+- [outputLengthLimit][outputLengthLimit]: limit total output length per `render()` call.
+- [maxDepth][maxDepth]: limit nesting depth of `{% render %}`, `{% include %}`, and `{% layout %}`.
+- Strftime numeric pad widths in the `date` filter are capped at `1_000_000` (1M) per conversion.
+
+These are cooperative safeguards, not runtime isolation—see [Production guidance](#production-guidance) below for host-level limits and online-service hardening.
## Limit details
@@ -26,9 +24,9 @@ The built-in limits are cooperative safeguards, not strict runtime isolation.
A typical PC handles `1e8` (100M) characters without issues.
-### renderLimit
+### templateLimit
-Restricting template size alone is insufficient because dynamic loops with large counts can occur during rendering. [renderLimit][renderLimit] mitigates this by limiting the time consumed by each `render()` call.
+Restricting template size alone is insufficient because dynamic loops with large counts can occur during rendering. [templateLimit][templateLimit] mitigates this by limiting the number of tag, HTML literal, and output nodes rendered in each `render()` call.
```liquid
{%- for i in (1..10000000) -%}
@@ -36,29 +34,19 @@ Restricting template size alone is insufficient because dynamic loops with large
{%- endfor -%}
```
-Render time is checked on a per-template basis (before rendering each template). In the above example, there are 2 templates in the loop: `order: ` and `{{i}}`, render time will be checked 10000000x2 times.
-
-`renderLimit` is not a hard CPU limiter. It is checked between template renders, so compute-intensive filters/tags/user-defined functions or deeply nested template execution between checks can still cause DoS.
+Each template node (the `for` tag, literal `order: `, output `{{i}}`, and so on) counts toward the limit. In the above example, a limit of `30000000` would be exceeded before the loop finishes.
-### memoryLimit
+`templateLimit` is checked before each node render, so compute-intensive filters/tags/user-defined functions between checks can still cause DoS.
-`memoryLimit` only limits operations that LiquidJS explicitly counts.
+### outputLengthLimit
-- Counted: memory-sensitive LiquidJS operations that call internal memory accounting.
-- Not guaranteed counted: arbitrary user object behavior such as custom `toValue()`/`toString()` chains, or other host-side code that allocates outside LiquidJS accounting points.
+[outputLengthLimit][outputLengthLimit] caps the cumulative length of output written during a `render()` call, including output from partials rendered via `{% render %}`.
-In other words, `memoryLimit` limits what LiquidJS counts, not every byte your process may allocate.
+### maxDepth
-Even with a small number of templates and iterations, memory usage can grow exponentially. In the following example, memory doubles with each iteration:
+[maxDepth][maxDepth] limits how deeply `{% render %}`, `{% include %}`, and `{% layout %}` can nest. Defaults to `128`. In sync rendering (`renderSync`), nested tags are driven by `toValueSync`, which recursively resumes each yielded generator on the call stack—deep nesting can overflow it, and `maxDepth` caps that depth. Async `render()` resumes the same tag generators via `toPromise`/`yield` without a deep synchronous call chain, so stack overflow is not a concern there (the limit still applies as a DoS guard).
-```liquid
-{% assign array = "1,2,3" | split: "," %}
-{% for i in (1..32) %}
- {% assign array = array | concat: array %}
-{% endfor %}
-```
-
-As [JavaScript uses GC to manage memory](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management), `memoryLimit` may not reflect the actual memory footprint.
+The `memoryLimit` option was removed in v11; enforce memory limits at the host or process level instead.
## `ownPropertyOnly` and scope data
@@ -68,20 +56,21 @@ With [`ownPropertyOnly`][ownPropertyOnly] `true`, plain scope objects only expos
[`Drop`][drop] values are not restricted the same way: LiquidJS still reads the prototype chain and may call [`liquidMethodMissing`][liquidMethodMissing]. **You** control what a drop exposes; narrow APIs and never feed unsafe data into drops unless the class is built for template access. `ownPropertyOnly` alone does not harden custom drops—audit them like any privileged code.
-## Online service guidance
-
-If you run an online service, avoid rendering fully user-defined templates whenever possible.
+## Production guidance
-- Prefer curated templates or a restricted template subset.
-- If user-defined templates are required, isolate rendering (worker/process/container), enforce OS/container memory and CPU limits, and apply request rate limits.
-- Treat `parseLimit`/`renderLimit`/`memoryLimit` as one layer in a broader DoS defense strategy.
+LiquidJS does not sandbox template code—custom filters, tags, and scope helpers run as ordinary JavaScript with your process privileges. Built-in DoS limits are one layer; production deployments, especially online services that accept template input, need additional hardening:
-For heavy single-template operations, process-level isolation is still recommended (for example with [paralleljs][paralleljs]).
+- **Prefer curated templates** over fully user-defined Liquid when possible; if users need customization, offer a restricted subset rather than open template editing.
+- Run each render in a **worker thread or child process** with a wall-clock timeout; **kill** the worker on expiry. Libraries such as [paralleljs][paralleljs] can help for heavy single-template work.
+- Enforce **container/Kubernetes cgroup limits**, `ulimit`, or equivalent on the renderer process for memory and CPU.
+- Apply **request rate limits** at the API or gateway layer.
+- **`node:vm`, `isolated-vm`, and Jinja/Twig-style sandbox modes are not a security boundary**—template logic runs in the same JS runtime as your app, with your privileges.
[paralleljs]: https://www.npmjs.com/package/paralleljs
[parseLimit]: /api/interfaces/LiquidOptions.html#parseLimit
-[renderLimit]: /api/interfaces/LiquidOptions.html#renderLimit
-[memoryLimit]: /api/interfaces/LiquidOptions.html#memoryLimit
+[templateLimit]: /api/interfaces/LiquidOptions.html#templateLimit
+[outputLengthLimit]: /api/interfaces/LiquidOptions.html#outputLengthLimit
+[maxDepth]: /api/interfaces/LiquidOptions.html#maxDepth
[ownPropertyOnly]: /api/interfaces/LiquidOptions.html#ownPropertyOnly
[renderOwnPropertyOnly]: /api/interfaces/RenderOptions.html#ownPropertyOnly
[strictVariables]: /api/interfaces/LiquidOptions.html#strictVariables
diff --git a/docs/themes/navy/source/js/main.js b/docs/themes/navy/source/js/main.js
index 1d4e5eb4cd..e55e35ea07 100644
--- a/docs/themes/navy/source/js/main.js
+++ b/docs/themes/navy/source/js/main.js
@@ -42,8 +42,7 @@
if (!/\/playground(?:\.html)?$/.test(location.pathname)) return;
updateVersion(liquidjs.version);
const engine = new liquidjs.Liquid({
- memoryLimit: 1e5,
- renderLimit: 1e5
+ templateLimit: 1e5
});
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
const editor = createEditor('editorEl', 'liquid');
diff --git a/src/context/context.ts b/src/context/context.ts
index 9dcb711ea7..fbc17f8d8c 100644
--- a/src/context/context.ts
+++ b/src/context/context.ts
@@ -1,4 +1,3 @@
-import { getPerformance } from '../util/performance'
import { Drop } from '../drop/drop'
import { __assign } from 'tslib'
import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options'
@@ -36,17 +35,19 @@ export class Context {
*/
public strictVariables: boolean;
public ownPropertyOnly: boolean;
- public memoryLimit: Limiter;
- public renderLimit: Limiter;
- public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, renderOptions: RenderOptions = {}, { memoryLimit, renderLimit }: { [key: string]: Limiter } = {}) {
+ public templateLimit: Limiter;
+ public outputLengthLimit: Limiter;
+ public depthLimit: Limiter;
+ public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, renderOptions: RenderOptions = {}, { templateLimit, outputLengthLimit, depthLimit }: { templateLimit?: Limiter, outputLengthLimit?: Limiter, depthLimit?: Limiter } = {}) {
this.sync = !!renderOptions.sync
this.opts = opts
this.globals = renderOptions.globals ?? opts.globals
this.environments = isObject(env) ? env : Object(env)
this.strictVariables = renderOptions.strictVariables ?? this.opts.strictVariables
this.ownPropertyOnly = renderOptions.ownPropertyOnly ?? opts.ownPropertyOnly
- this.memoryLimit = memoryLimit ?? new Limiter('memory alloc', renderOptions.memoryLimit ?? opts.memoryLimit)
- this.renderLimit = renderLimit ?? new Limiter('template render', getPerformance().now() + (renderOptions.renderLimit ?? opts.renderLimit))
+ this.templateLimit = templateLimit ?? new Limiter('template', renderOptions.templateLimit ?? opts.templateLimit)
+ this.outputLengthLimit = outputLengthLimit ?? new Limiter('output length', renderOptions.outputLengthLimit ?? opts.outputLengthLimit)
+ this.depthLimit = depthLimit ?? new Limiter('template depth', opts.maxDepth)
}
public getRegister (key: string, defaultValue: T = undefined as T): T {
return (this.registers[key] = this.registers[key] || defaultValue)
@@ -109,8 +110,9 @@ export class Context {
strictVariables: this.strictVariables,
ownPropertyOnly: this.ownPropertyOnly
}, {
- renderLimit: this.renderLimit,
- memoryLimit: this.memoryLimit
+ templateLimit: this.templateLimit,
+ outputLengthLimit: this.outputLengthLimit,
+ depthLimit: this.depthLimit
})
}
private findScope (key: string | number) {
diff --git a/src/emitters/simple-emitter.ts b/src/emitters/simple-emitter.ts
index f1d0482068..3de1e368f5 100644
--- a/src/emitters/simple-emitter.ts
+++ b/src/emitters/simple-emitter.ts
@@ -1,10 +1,17 @@
-import { stringify } from '../util'
+import { stringify, Limiter } from '../util'
import { Emitter } from './emitter'
export class SimpleEmitter implements Emitter {
public buffer = '';
+ private outputLengthLimit?: Limiter
+
+ constructor (outputLengthLimit?: Limiter) {
+ this.outputLengthLimit = outputLengthLimit
+ }
public write (html: any) {
- this.buffer += stringify(html)
+ const str = stringify(html)
+ this.outputLengthLimit?.use(str.length)
+ this.buffer += str
}
}
diff --git a/src/emitters/streamed-emitter.ts b/src/emitters/streamed-emitter.ts
index 6750ea3b4e..a9c93e4c5b 100644
--- a/src/emitters/streamed-emitter.ts
+++ b/src/emitters/streamed-emitter.ts
@@ -1,12 +1,20 @@
-import { stringify } from '../util'
+import { stringify, Limiter } from '../util'
import { Emitter } from './emitter'
import { PassThrough } from 'stream'
export class StreamedEmitter implements Emitter {
public buffer = '';
public stream: NodeJS.ReadWriteStream = new PassThrough()
+ private outputLengthLimit?: Limiter
+
+ constructor (outputLengthLimit?: Limiter) {
+ this.outputLengthLimit = outputLengthLimit
+ }
+
public write (html: any) {
- this.stream.write(stringify(html))
+ const str = stringify(html)
+ this.outputLengthLimit?.use(str.length)
+ this.stream.write(str)
}
public error (err: Error) {
this.stream.emit('error', err)
diff --git a/src/filters/array.ts b/src/filters/array.ts
index bf16b59511..2a5cb8a0e0 100644
--- a/src/filters/array.ts
+++ b/src/filters/array.ts
@@ -8,9 +8,6 @@ import { EmptyDrop } from '../drop'
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
const array = toArray(v)
const sep = isNil(arg) ? ' ' : stringify(arg)
- let outputSize = sep.length * Math.max(array.length - 1, 0)
- for (let i = 0; i < array.length; i++) outputSize += String(array[i]).length
- this.context.memoryLimit.use(outputSize)
return Array.prototype.join.call(array, sep)
})
export const last = argumentsToValue(function (this: FilterImpl, v: any) {
@@ -21,14 +18,12 @@ export const first = argumentsToValue(function (this: FilterImpl, v: any) {
})
export const reverse = argumentsToValue(function (this: FilterImpl, v: any[]) {
const array = toArray(v)
- this.context.memoryLimit.use(array.length)
return [...array].reverse()
})
function * sortBy (this: FilterImpl, arr: T[], property: string | undefined, comparator: (a: unknown, b: unknown) => number): IterableIterator {
const values: [T, unknown][] = []
const array = toArray(arr)
- this.context.memoryLimit.use(array.length)
for (const item of array) {
values.push([
item,
@@ -51,7 +46,6 @@ export const size = (v: string | any[]) => v?.length || 0
export function * map (this: FilterImpl, arr: Scope[], property: string): IterableIterator {
const results = []
const array = toArray(arr)
- this.context.memoryLimit.use(array.length)
for (const item of array) {
results.push(yield this.context._getFromScope(item, stringify(property), false))
}
@@ -70,14 +64,12 @@ export function * sum (this: FilterImpl, arr: Scope[], property?: string): Itera
export function compact (this: FilterImpl, arr: T[]) {
const array = toArray(arr)
- this.context.memoryLimit.use(array.length)
return Array.prototype.filter.call(array, x => !isNil(toValue(x)))
}
export function concat (this: FilterImpl, v: T1[], arg: T2[] = []): (T1 | T2)[] {
const lhs = toArray(v)
const rhs = toArray(arg)
- this.context.memoryLimit.use(lhs.length + rhs.length)
return Array.prototype.concat.call(lhs, rhs)
}
@@ -87,7 +79,6 @@ export function push (this: FilterImpl, v: T[], arg: T): T[] {
export function unshift (this: FilterImpl, v: T[], arg: T): T[] {
const array = toArray(v)
- this.context.memoryLimit.use(array.length)
const clone = [...array]
clone.unshift(arg)
return clone
@@ -95,7 +86,6 @@ export function unshift (this: FilterImpl, v: T[], arg: T): T[] {
export function pop (this: FilterImpl, v: T[]): T[] {
const array = toArray(v)
- this.context.memoryLimit.use(array.length)
const clone = [...array]
clone.pop()
return clone
@@ -103,7 +93,6 @@ export function pop (this: FilterImpl, v: T[]): T[] {
export function shift (this: FilterImpl, v: T[]): T[] {
const array = toArray(v)
- this.context.memoryLimit.use(array.length)
const clone = [...array]
clone.shift()
return clone
@@ -114,7 +103,6 @@ export function slice (this: FilterImpl, v: T[] | string, begin: number, leng
if (isNil(v)) return []
if (!isArray(v)) v = stringify(v)
begin = begin < 0 ? v.length + begin : begin
- this.context.memoryLimit.use(length)
return isArray(v)
? Array.prototype.slice.call(v, begin, begin + length)
: String.prototype.slice.call(v, begin, begin + length)
@@ -133,7 +121,6 @@ function expectedMatcher (this: FilterImpl, expected: any): (v: any) => boolean
function * filter (this: FilterImpl, include: boolean, arr: T[], property: string, expected: any): IterableIterator {
const values: unknown[] = []
arr = toArray(arr)
- this.context.memoryLimit.use(arr.length)
const token = new Tokenizer(stringify(property)).readScopeValue()
for (const item of arr) {
values.push(yield evalToken(token, this.context.spawn(item)))
@@ -146,7 +133,6 @@ function * filter_exp (this: FilterImpl, include: boolean, arr
const filtered: unknown[] = []
const keyTemplate = new Value(stringify(exp), this.liquid)
const array = toArray(arr)
- this.context.memoryLimit.use(array.length)
for (const item of array) {
this.context.push({ [itemName]: item })
const value = yield keyTemplate.value(this.context)
@@ -176,7 +162,6 @@ export function * group_by (this: FilterImpl, arr: T[], proper
const map = new Map()
arr = toEnumerable(arr)
const token = new Tokenizer(stringify(property)).readScopeValue()
- this.context.memoryLimit.use(arr.length)
for (const item of arr) {
const key = yield evalToken(token, this.context.spawn(item))
if (!map.has(key)) map.set(key, [])
@@ -189,7 +174,6 @@ export function * group_by_exp (this: FilterImpl, arr: T[], it
const map = new Map()
const keyTemplate = new Value(stringify(exp), this.liquid)
arr = toEnumerable(arr)
- this.context.memoryLimit.use(arr.length)
for (const item of arr) {
this.context.push({ [itemName]: item })
const key = yield keyTemplate.value(this.context)
@@ -253,7 +237,6 @@ export function * find_exp (this: FilterImpl, arr: T[], itemNa
export function uniq (this: FilterImpl, arr: T[]): T[] {
arr = toArray(arr)
- this.context.memoryLimit.use(arr.length)
return [...new Set(arr)]
}
@@ -261,7 +244,6 @@ export function sample (this: FilterImpl, v: T[] | string, count = 1): T | st
v = toValue(v)
if (isNil(v)) return []
if (!isArray(v)) v = stringify(v)
- this.context.memoryLimit.use(v.length)
const shuffled = [...v].sort(() => Math.random() - 0.5)
if (count === 1) return shuffled[0]
return shuffled.slice(0, count)
diff --git a/src/filters/base64.ts b/src/filters/base64.ts
index 29ef84e52c..5324c74732 100644
--- a/src/filters/base64.ts
+++ b/src/filters/base64.ts
@@ -10,16 +10,13 @@ import { base64Encode, base64Decode } from './base64-impl'
export function base64_encode (this: FilterImpl, value: string | Buffer): string {
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
- this.context.memoryLimit.use(value.byteLength)
return value.toString('base64')
}
const str = stringify(value)
- this.context.memoryLimit.use(str.length)
return base64Encode(str)
}
export function base64_decode (this: FilterImpl, value: string): string {
const str = stringify(value)
- this.context.memoryLimit.use(str.length)
return base64Decode(str)
}
diff --git a/src/filters/crypto.ts b/src/filters/crypto.ts
index 7210523bde..26caebd267 100644
--- a/src/filters/crypto.ts
+++ b/src/filters/crypto.ts
@@ -10,13 +10,11 @@ import { sha256 as sha256Impl, hmacSha256 as hmacSha256Impl } from './crypto-imp
export function sha256 (this: FilterImpl, value: unknown): string | Promise {
const str = stringify(value)
- this.context.memoryLimit.use(str.length)
return sha256Impl(str)
}
export function hmac_sha256 (this: FilterImpl, value: unknown, key: unknown): string | Promise {
const str = stringify(value)
const keyStr = stringify(key)
- this.context.memoryLimit.use(str.length + keyStr.length)
return hmacSha256Impl(str, keyStr)
}
diff --git a/src/filters/date.ts b/src/filters/date.ts
index 07665c9579..e2567421b1 100644
--- a/src/filters/date.ts
+++ b/src/filters/date.ts
@@ -3,14 +3,11 @@ import { FilterImpl } from '../template'
import { NormalizedFullOptions } from '../liquid-options'
export function date (this: FilterImpl, v: string | Date, format?: string, timezoneOffset?: number | string) {
- const size = ((v as string)?.length ?? 0) + ((timezoneOffset as string)?.length ?? 0)
- this.context.memoryLimit.use(size)
const date = parseDate(v, this.context.opts, timezoneOffset)
if (!date) return v
format = toValue(format)
format = isNil(format) ? this.context.opts.dateFormat : stringify(format)
- this.context.memoryLimit.use(format.length)
- return strftime(date, format, this.context.memoryLimit)
+ return strftime(date, format)
}
export function date_to_xmlschema (this: FilterImpl, v: string | Date) {
@@ -32,14 +29,13 @@ export function date_to_long_string (this: FilterImpl, v: string | Date, type?:
function stringify_date (this: FilterImpl, v: string | Date, month_type: string, type?: string, style?: string) {
const date = parseDate(v, this.context.opts)
if (!date) return v
- const ml = this.context.memoryLimit
if (type === 'ordinal') {
const d = date.getDate()
return style === 'US'
- ? strftime(date, `${month_type} ${d}%q, %Y`, ml)
- : strftime(date, `${d}%q ${month_type} %Y`, ml)
+ ? strftime(date, `${month_type} ${d}%q, %Y`)
+ : strftime(date, `${d}%q ${month_type} %Y`)
}
- return strftime(date, `%d ${month_type} %Y`, ml)
+ return strftime(date, `%d ${month_type} %Y`)
}
function parseDate (v: string | Date, opts: NormalizedFullOptions, timezoneOffset?: number | string): LiquidDate | undefined {
diff --git a/src/filters/html.ts b/src/filters/html.ts
index b52f1b3da2..01cd67d654 100644
--- a/src/filters/html.ts
+++ b/src/filters/html.ts
@@ -18,7 +18,6 @@ const unescapeMap: Record = {
export function escape (this: FilterImpl, str: string) {
str = stringify(str)
- this.context.memoryLimit.use(str.length)
return str.replace(/&|<|>|"|'/g, m => escapeMap[m])
}
@@ -28,7 +27,6 @@ export function xml_escape (this: FilterImpl, str: string) {
function unescape (this: FilterImpl, str: string) {
str = stringify(str)
- this.context.memoryLimit.use(str.length)
return str.replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
}
@@ -38,7 +36,6 @@ export function escape_once (this: FilterImpl, str: string) {
export function newline_to_br (this: FilterImpl, v: string) {
const str = stringify(v)
- this.context.memoryLimit.use(str.length)
return str.replace(/\r?\n/gm, '
\n')
}
@@ -46,7 +43,6 @@ export function newline_to_br (this: FilterImpl, v: string) {
// equivalent is O(n^2) in V8 on unclosed openers.
export function strip_html (this: FilterImpl, v: string) {
const str = stringify(v)
- this.context.memoryLimit.use(str.length)
const blocks = new Map([[' in linear time', () => {
const liquid = new Liquid()
const payload = '