chore: more strict mem limit for string filters

This commit is contained in:
Harttle
2026-03-22 22:10:39 +08:00
parent 95ddefc056
commit 94440a0653
3 changed files with 47 additions and 30 deletions
+44 -23
View File
@@ -11,7 +11,7 @@
// Hiragana (Japanese): \u3040-\u309F // Hiragana (Japanese): \u3040-\u309F
// Hangul (Korean): \uAC00-\uD7AF // Hangul (Korean): \uAC00-\uD7AF
import { FilterImpl } from '../template' import { FilterImpl } from '../template'
import { assert, escapeRegExp, stringify } from '../util' import { assert, stringify } from '../util'
const rCJKWord = /[\u4E00-\u9FFF\uF900-\uFAFF\u3400-\u4DBF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/gu const rCJKWord = /[\u4E00-\u9FFF\uF900-\uFAFF\u3400-\u4DBF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/gu
@@ -38,10 +38,14 @@ export function lstrip (this: FilterImpl, v: string, chars?: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) this.context.memoryLimit.use(str.length)
if (chars) { if (chars) {
chars = escapeRegExp(stringify(chars)) chars = stringify(chars)
return str.replace(new RegExp(`^[${chars}]+`, 'g'), '') this.context.memoryLimit.use(chars.length)
for (let i = 0, set = new Set(chars); i < str.length; i++) {
if (!set.has(str[i])) return str.slice(i)
}
return ''
} }
return str.replace(/^\s+/, '') return str.trimStart()
} }
export function downcase (this: FilterImpl, v: string) { export function downcase (this: FilterImpl, v: string) {
@@ -58,20 +62,22 @@ export function upcase (this: FilterImpl, v: string) {
export function remove (this: FilterImpl, v: string, arg: string) { export function remove (this: FilterImpl, v: string, arg: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) arg = stringify(arg)
return str.split(stringify(arg)).join('') this.context.memoryLimit.use(str.length + arg.length)
return str.split(arg).join('')
} }
export function remove_first (this: FilterImpl, v: string, l: string) { export function remove_first (this: FilterImpl, v: string, l: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) l = stringify(l)
return str.replace(stringify(l), '') this.context.memoryLimit.use(str.length + l.length)
return str.replace(l, '')
} }
export function remove_last (this: FilterImpl, v: string, l: string) { export function remove_last (this: FilterImpl, v: string, l: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length)
const pattern = stringify(l) const pattern = stringify(l)
this.context.memoryLimit.use(str.length + pattern.length)
const index = str.lastIndexOf(pattern) const index = str.lastIndexOf(pattern)
if (index === -1) return str if (index === -1) return str
return str.substring(0, index) + str.substring(index + pattern.length) return str.substring(0, index) + str.substring(index + pattern.length)
@@ -81,10 +87,14 @@ export function rstrip (this: FilterImpl, str: string, chars?: string) {
str = stringify(str) str = stringify(str)
this.context.memoryLimit.use(str.length) this.context.memoryLimit.use(str.length)
if (chars) { if (chars) {
chars = escapeRegExp(stringify(chars)) chars = stringify(chars)
return str.replace(new RegExp(`[${chars}]+$`, 'g'), '') this.context.memoryLimit.use(chars.length)
for (let i = str.length - 1, set = new Set(chars); i >= 0; i--) {
if (!set.has(str[i])) return str.slice(0, i + 1)
}
return ''
} }
return str.replace(/\s+$/, '') return str.trimEnd()
} }
export function split (this: FilterImpl, v: string, arg: string) { export function split (this: FilterImpl, v: string, arg: string) {
@@ -101,10 +111,13 @@ export function strip (this: FilterImpl, v: string, chars?: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) this.context.memoryLimit.use(str.length)
if (chars) { if (chars) {
chars = escapeRegExp(stringify(chars)) const set = new Set(stringify(chars))
return str this.context.memoryLimit.use(set.size)
.replace(new RegExp(`^[${chars}]+`, 'g'), '') let i = 0
.replace(new RegExp(`[${chars}]+$`, 'g'), '') let j = str.length - 1
while (set.has(str[i])) i++
while (j >= i && set.has(str[j])) j--
return str.slice(i, j + 1)
} }
return str.trim() return str.trim()
} }
@@ -123,36 +136,43 @@ export function capitalize (this: FilterImpl, str: string) {
export function replace (this: FilterImpl, v: string, pattern: string, replacement: string) { export function replace (this: FilterImpl, v: string, pattern: string, replacement: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) pattern = stringify(pattern)
return str.split(stringify(pattern)).join(replacement) this.context.memoryLimit.use(str.length + pattern.length + replacement.length)
return str.split(pattern).join(replacement)
} }
export function replace_first (this: FilterImpl, v: string, arg1: string, arg2: string) { export function replace_first (this: FilterImpl, v: string, arg1: string, arg2: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) arg1 = stringify(arg1)
return str.replace(stringify(arg1), arg2) arg2 = stringify(arg2)
this.context.memoryLimit.use(str.length + arg1.length + arg2.length)
return str.replace(arg1, arg2)
} }
export function replace_last (this: FilterImpl, v: string, arg1: string, arg2: string) { export function replace_last (this: FilterImpl, v: string, arg1: string, arg2: string) {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) this.context.memoryLimit.use(str.length)
const pattern = stringify(arg1) const pattern = stringify(arg1)
this.context.memoryLimit.use(pattern.length)
const index = str.lastIndexOf(pattern) const index = str.lastIndexOf(pattern)
if (index === -1) return str if (index === -1) return str
const replacement = stringify(arg2) const replacement = stringify(arg2)
this.context.memoryLimit.use(replacement.length)
return str.substring(0, index) + replacement + str.substring(index + pattern.length) return str.substring(0, index) + replacement + str.substring(index + pattern.length)
} }
export function truncate (this: FilterImpl, v: string, l = 50, o = '...') { export function truncate (this: FilterImpl, v: string, l = 50, o = '...') {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) o = stringify(o)
this.context.memoryLimit.use(str.length + o.length)
if (str.length <= l) return v if (str.length <= l) return v
return str.substring(0, l - o.length) + o return str.substring(0, l - o.length) + o
} }
export function truncatewords (this: FilterImpl, v: string, words = 15, o = '...') { export function truncatewords (this: FilterImpl, v: string, words = 15, o = '...') {
const str = stringify(v) const str = stringify(v)
this.context.memoryLimit.use(str.length) o = stringify(o)
this.context.memoryLimit.use(str.length + o.length)
const arr = str.split(/\s+/) const arr = str.split(/\s+/)
if (words <= 0) words = 1 if (words <= 0) words = 1
let ret = arr.slice(0, words).join(' ') let ret = arr.slice(0, words).join(' ')
@@ -187,7 +207,8 @@ export function number_of_words (this: FilterImpl, input: string, mode?: 'cjk' |
} }
export function array_to_sentence_string (this: FilterImpl, array: unknown[], connector = 'and') { export function array_to_sentence_string (this: FilterImpl, array: unknown[], connector = 'and') {
this.context.memoryLimit.use(array.length) connector = stringify(connector)
this.context.memoryLimit.use(array.length + connector.length)
switch (array.length) { switch (array.length) {
case 0: case 0:
return '' return ''
+3 -3
View File
@@ -1,5 +1,5 @@
import { FS } from './fs' import { FS } from './fs'
import { assert, escapeRegex } from '../util' import { assert } from '../util'
export interface LoaderOptions { export interface LoaderOptions {
fs: FS; fs: FS;
@@ -24,8 +24,8 @@ export class Loader {
if (options.relativeReference) { if (options.relativeReference) {
const sep = options.fs.sep const sep = options.fs.sep
assert(sep, '`fs.sep` is required for relative reference') assert(sep, '`fs.sep` is required for relative reference')
const rRelativePath = new RegExp(['.' + sep, '..' + sep, './', '../'].map(prefix => escapeRegex(prefix)).join('|')) const prefixes = ['.' + sep, '..' + sep, './', '../']
this.shouldLoadRelative = (referencedFile: string) => rRelativePath.test(referencedFile) this.shouldLoadRelative = (referencedFile: string) => prefixes.some(prefix => referencedFile.startsWith(prefix))
} else { } else {
this.shouldLoadRelative = (_referencedFile: string) => false this.shouldLoadRelative = (_referencedFile: string) => false
} }
-4
View File
@@ -22,10 +22,6 @@ export function isIterator (val: any): val is IterableIterator<any> {
return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return) return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return)
} }
export function escapeRegex (str: string) {
return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
}
export function promisify<T1, T2> (fn: (arg1: T1, cb: (err: Error | null, result: T2) => void) => void): (arg1: T1) => Promise<T2>; export function promisify<T1, T2> (fn: (arg1: T1, cb: (err: Error | null, result: T2) => void) => void): (arg1: T1) => Promise<T2>;
export function promisify<T1, T2, T3> (fn: (arg1: T1, arg2: T2, cb: (err: Error | null, result: T3) => void) => void): (arg1: T1, arg2: T2) => Promise<T3>; export function promisify<T1, T2, T3> (fn: (arg1: T1, arg2: T2, cb: (err: Error | null, result: T3) => void) => void): (arg1: T1, arg2: T2) => Promise<T3>;
export function promisify (fn: any) { export function promisify (fn: any) {