Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/strata/diagram.ts

Raw
/** A deliberately narrow Mermaid source profile; syntax errors remain renderer errors. */
export function validateDiagram(source: string): string {
	if (typeof source !== "string" || !source.trim() || source.length > 12_000) {
		throw new Error("diagram must contain 1 to 12000 characters");
	}
	// No configuration, HTML/entities, CSS shorthand, or URI-bearing features.
	if (
		/[\u0000-\u0008\u000b-\u001f]/u.test(source) ||
		/%%\s*\{|^\s*---|:::/mu.test(source) ||
		/<\/?[A-Za-z!][^>]*>|&(?:#\w+|[A-Za-z]+);/u.test(source) ||
		/\b(?:https?|mailto|javascript|data):|\b(?:href|xlink:href)\s*=/iu.test(
			source,
		)
	)
		throw new Error(
			"diagram directives, HTML, links, or styling are not allowed",
		);

	let type = "";
	for (const rawLine of source.split("\n")) {
		const line = rawLine.trim();
		if (!line || line.startsWith("%%")) continue;
		if (!type) {
			const header =
				/^(flowchart\s+(?:TB|TD|BT|RL|LR)|sequenceDiagram|stateDiagram-v2|classDiagram|erDiagram)\s*;?$/u.exec(
					line,
				);
			if (!header) throw new Error("unsupported diagram type or header");
			type = line.split(/\s/u)[0] ?? "";
			continue;
		}
		for (const statement of line.split(";")) {
			const trimmed = statement.trim();
			if (
				/^(?:click|links?|style|classDef|linkStyle|cssClass|callback)\b/iu.test(
					trimmed,
				) ||
				(/^class\b/iu.test(trimmed) && type !== "classDiagram") ||
				/@\s*\{/u.test(trimmed)
			)
				throw new Error("diagram links or styling are not allowed");
		}
	}
	if (!type) throw new Error("diagram type is required");
	return source;
}