hardening
This commit is contained in:
56
parser.ts
56
parser.ts
@@ -1,5 +1,6 @@
|
||||
import { readdirSync, statSync, readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { TextDecoder } from "node:util"
|
||||
|
||||
export type TemplateVariable = {
|
||||
path: string
|
||||
@@ -8,10 +9,12 @@ export type TemplateVariable = {
|
||||
|
||||
const IF_RE = /<@(?:if|elseif)\((.+?)\)>/g
|
||||
const VAR_RE = /<@var\(context\.(.+?)(?::(\w+))?\)>/g
|
||||
const DIRECTIVE_RE = /<@(if|elseif|else|endif)(?:\((.+?)\))?>/g
|
||||
const EQ_RE = /^eq\(context\.(.+?),\s*"(.*)"\)$/
|
||||
const PATH_RE = /^context\.(.+)$/
|
||||
|
||||
function extractCondition(expr: string, vars: TemplateVariable[]) {
|
||||
function extractCondition(expr: string | undefined, vars: TemplateVariable[]) {
|
||||
if (!expr) throw new Error("Missing condition expression")
|
||||
const eqMatch = expr.match(EQ_RE)
|
||||
if (eqMatch) {
|
||||
vars.push({ path: eqMatch[1]!, type: "string" })
|
||||
@@ -20,7 +23,9 @@ function extractCondition(expr: string, vars: TemplateVariable[]) {
|
||||
const pathMatch = expr.match(PATH_RE)
|
||||
if (pathMatch) {
|
||||
vars.push({ path: pathMatch[1]!, type: "boolean" })
|
||||
return
|
||||
}
|
||||
throw new Error(`Invalid condition expression: ${expr}`)
|
||||
}
|
||||
|
||||
function extractFromString(text: string, vars: TemplateVariable[]) {
|
||||
@@ -32,6 +37,47 @@ function extractFromString(text: string, vars: TemplateVariable[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function validateIfBlocks(content: string, vars: TemplateVariable[]) {
|
||||
const stack: { sawElse: boolean }[] = []
|
||||
|
||||
for (const match of content.matchAll(DIRECTIVE_RE)) {
|
||||
const directive = match[1]!
|
||||
const condition = match[2]
|
||||
|
||||
if (directive === "if") {
|
||||
extractCondition(condition!, vars)
|
||||
stack.push({ sawElse: false })
|
||||
} else if (directive === "elseif") {
|
||||
const frame = stack[stack.length - 1]
|
||||
if (!frame) throw new Error("Unexpected <@elseif> without <@if>")
|
||||
if (frame.sawElse) throw new Error("Unexpected <@elseif> after <@else>")
|
||||
extractCondition(condition!, vars)
|
||||
} else if (directive === "else") {
|
||||
const frame = stack[stack.length - 1]
|
||||
if (!frame) throw new Error("Unexpected <@else> without <@if>")
|
||||
if (frame.sawElse) throw new Error("Unexpected duplicate <@else>")
|
||||
frame.sawElse = true
|
||||
} else if (directive === "endif") {
|
||||
if (stack.length === 0) throw new Error("Unexpected <@endif> without <@if>")
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.length > 0) {
|
||||
throw new Error("Unmatched <@if> without <@endif>")
|
||||
}
|
||||
}
|
||||
|
||||
function isUtf8Text(buffer: Buffer): boolean {
|
||||
if (buffer.indexOf(0) !== -1) return false
|
||||
try {
|
||||
new TextDecoder("utf-8", { fatal: true }).decode(buffer)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function walkDir(dirPath: string, vars: TemplateVariable[]) {
|
||||
const entries = readdirSync(dirPath).sort()
|
||||
for (const entry of entries) {
|
||||
@@ -42,8 +88,12 @@ function walkDir(dirPath: string, vars: TemplateVariable[]) {
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(fullPath, vars)
|
||||
} else if (stat.isFile()) {
|
||||
const content = readFileSync(fullPath, "utf-8")
|
||||
extractFromString(content, vars)
|
||||
const content = readFileSync(fullPath)
|
||||
if (isUtf8Text(content)) {
|
||||
const text = content.toString("utf-8")
|
||||
extractFromString(text, vars)
|
||||
validateIfBlocks(text, vars)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user