Files
workinf_Blender_Wasm/tools/web/server-job-isolation.mjs
mes123456 380cbed4ff
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

54 lines
3.3 KiB
JavaScript

import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
export const SERVER_JOB_DIRECTORY_SCHEMA = 1;
const JOB_ID = /^[-A-Za-z0-9:_./]{1,256}$/;
function validateJobId(jobId) {
if (typeof jobId !== "string" || !JOB_ID.test(jobId) || jobId.includes("..")) throw new Error("SERVER_JOB_DIRECTORY_INVALID: jobId is invalid");
}
function validateRoot(root) {
if (typeof root !== "string" || !path.isAbsolute(root)) throw new Error("SERVER_JOB_DIRECTORY_INVALID: root must be absolute");
return path.resolve(root);
}
export async function createServerJobDirectory(root, jobId) {
const resolvedRoot = validateRoot(root);
validateJobId(jobId);
await fs.mkdir(resolvedRoot, { recursive: true, mode: 0o700 });
const randomPrefix = `.blender-job-${crypto.randomUUID()}-`;
const directory = await fs.mkdtemp(path.join(resolvedRoot, randomPrefix), { encoding: "utf8" });
await fs.chmod(directory, 0o700);
return Object.freeze({ schemaVersion: SERVER_JOB_DIRECTORY_SCHEMA, jobId, root: resolvedRoot, path: directory, directoryName: path.basename(directory), state: "ALLOCATED", cleanupCount: 0 });
}
export async function cleanupServerJobDirectory(job) {
if (!job || job.schemaVersion !== SERVER_JOB_DIRECTORY_SCHEMA || typeof job.path !== "string" || typeof job.root !== "string") throw new Error("SERVER_JOB_DIRECTORY_INVALID: receipt is invalid");
if (job.state === "CLEANED") return job;
const root = validateRoot(job.root);
const directory = path.resolve(job.path);
if (path.dirname(directory) !== root || !path.basename(directory).startsWith(".blender-job-")) throw new Error("SERVER_JOB_DIRECTORY_INVALID: directory escaped the job root");
await fs.chmod(directory, 0o700);
try { await fs.chmod(path.join(directory, "source"), 0o700); } catch (error) { if (error?.code !== "ENOENT") throw error; }
await fs.rm(directory, { recursive: true, force: false });
return Object.freeze({ ...job, state: "CLEANED", cleanupCount: 1 });
}
export async function prepareServerJobWorkspace(job, sourceBytes, sourceName = "source.blend") {
if (!job || job.schemaVersion !== SERVER_JOB_DIRECTORY_SCHEMA || job.state !== "ALLOCATED") throw new Error("SERVER_JOB_WORKSPACE_INVALID: job receipt is not allocated");
if (!(sourceBytes instanceof Uint8Array) || sourceBytes.byteLength < 1) throw new Error("SERVER_JOB_WORKSPACE_INVALID: source bytes are empty");
if (typeof sourceName !== "string" || !/^[A-Za-z0-9_.-]{1,128}$/.test(sourceName) || sourceName.includes("..")) throw new Error("SERVER_JOB_WORKSPACE_INVALID: source name is invalid");
const sourceDirectory = path.join(job.path, "source");
const outputDirectory = path.join(job.path, "output");
await fs.mkdir(sourceDirectory, { mode: 0o700 });
await fs.mkdir(outputDirectory, { mode: 0o700 });
const sourcePath = path.join(sourceDirectory, sourceName);
await fs.writeFile(sourcePath, sourceBytes, { mode: 0o444, flag: "wx" });
await fs.chmod(sourceDirectory, 0o555);
await fs.chmod(sourcePath, 0o444);
await fs.chmod(outputDirectory, 0o700);
return Object.freeze({ schemaVersion: SERVER_JOB_DIRECTORY_SCHEMA, sourceDirectory, sourcePath, outputDirectory, sourceMode: "0444", sourceDirectoryMode: "0555", outputDirectoryMode: "0700", sourceReadOnly: true, outputWritable: true });
}