Files
workinf_Blender_Wasm/web/protocol/asset-path.ts
mes123456 380cbed4ff
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

65 lines
2.2 KiB
TypeScript

const DRIVE_PATH = /^[A-Za-z]:[\\/]/;
const URI_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:/;
const CONTROL_CHARACTER = /[\u0000-\u001f\u007f]/;
function invalidPath(message: "ASSET_PATH_INVALID" | "ASSET_PATH_OUTSIDE_PROJECT"): never {
throw new Error(message);
}
function decodePath(sourcePath: string): string {
let decoded: string;
try {
decoded = decodeURIComponent(sourcePath);
}
catch {
return invalidPath("ASSET_PATH_INVALID");
}
// A canonical path must be safe to normalize again. Residual percent octets could otherwise
// become separators or dot segments in a second decoder.
if (decoded.includes("%")) return invalidPath("ASSET_PATH_OUTSIDE_PROJECT");
try {
encodeURIComponent(decoded);
}
catch {
return invalidPath("ASSET_PATH_INVALID");
}
return decoded.normalize("NFC");
}
export function normalizeProjectAssetPath(sourcePath: string): string {
if (typeof sourcePath !== "string" || sourcePath.length === 0 || sourcePath.length > 2048) {
return invalidPath("ASSET_PATH_INVALID");
}
const blenderRelative = sourcePath.startsWith("//");
if (sourcePath.startsWith("\\") || sourcePath.startsWith("/") && !blenderRelative) {
return invalidPath("ASSET_PATH_OUTSIDE_PROJECT");
}
let relative = decodePath(sourcePath);
if (relative.startsWith("//")) {
if (!blenderRelative) return invalidPath("ASSET_PATH_OUTSIDE_PROJECT");
relative = relative.slice(2);
}
relative = relative.replaceAll("\\", "/");
if (relative.startsWith("/") || DRIVE_PATH.test(relative) || URI_SCHEME.test(relative)) {
return invalidPath("ASSET_PATH_OUTSIDE_PROJECT");
}
const canonicalSegments: string[] = [];
for (const segment of relative.split("/")) {
if (segment.length === 0 || segment === ".") continue;
if (segment === "..") {
if (canonicalSegments.length === 0) return invalidPath("ASSET_PATH_OUTSIDE_PROJECT");
canonicalSegments.pop();
continue;
}
if (CONTROL_CHARACTER.test(segment)) return invalidPath("ASSET_PATH_OUTSIDE_PROJECT");
canonicalSegments.push(segment);
}
const canonical = canonicalSegments.join("/");
if (!canonical || canonical.length > 2048) return invalidPath("ASSET_PATH_INVALID");
return canonical;
}