93 lines
4.7 KiB
JavaScript
93 lines
4.7 KiB
JavaScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const toolsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
const projectRoot = path.resolve(toolsRoot, "..");
|
|
const docsRoot = path.join(projectRoot, "docs");
|
|
const packageRoot = path.join(projectRoot, "web", "offline-package");
|
|
const fcstdSource = path.join(projectRoot, "freecad-source", "build-web", "configure", "bin", "WebBox.FCStd");
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-web-upgrade-corrupt-"));
|
|
|
|
function sha256(filePath) { return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex"); }
|
|
function canonical(value) { return `${JSON.stringify(value, null, 2)}\n`; }
|
|
function walk(root) {
|
|
const files = [];
|
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
const absolute = path.join(root, entry.name);
|
|
if (entry.isDirectory()) files.push(...walk(absolute)); else if (entry.isFile()) files.push(absolute);
|
|
}
|
|
return files.sort();
|
|
}
|
|
function validatePackage(packageDir) {
|
|
const manifestPath = path.join(packageDir, "manifest.json");
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
const canonicalManifest = { ...manifest, manifestFile: { ...manifest.manifestFile, selfSha256: null } };
|
|
const selfHash = crypto.createHash("sha256").update(canonical(canonicalManifest), "utf8").digest("hex");
|
|
if (selfHash !== manifest.manifestFile.selfSha256) throw new Error("manifest self hash mismatch");
|
|
for (const entry of manifest.files) {
|
|
const file = path.join(packageDir, entry.path);
|
|
if (!fs.existsSync(file)) throw new Error(`missing ${entry.path}`);
|
|
if (fs.statSync(file).size !== entry.size || sha256(file) !== entry.sha256) throw new Error(`hash mismatch ${entry.path}`);
|
|
}
|
|
return manifest;
|
|
}
|
|
function copyPackage(destination) { fs.cpSync(packageRoot, destination, { recursive: true }); }
|
|
|
|
const cases = [
|
|
{ id: "wasm", path: "kernel/FreeCADWebKernel.wasm", mutation: (file) => fs.appendFileSync(file, Buffer.from([0x00])) },
|
|
{ id: "entry", path: "assets/index-DKKTEr2e.js", mutation: (file) => fs.appendFileSync(file, "\n// corrupt upgrade fixture\n") },
|
|
{ id: "manifest", path: "manifest.json", mutation: (file) => fs.appendFileSync(file, "\ncorrupt") },
|
|
];
|
|
const results = [];
|
|
for (const testCase of cases) {
|
|
const caseRoot = path.join(tempRoot, testCase.id);
|
|
const activeRoot = path.join(caseRoot, "active");
|
|
const candidateRoot = path.join(caseRoot, "candidate");
|
|
fs.mkdirSync(caseRoot, { recursive: true });
|
|
copyPackage(activeRoot);
|
|
copyPackage(candidateRoot);
|
|
const activeManifest = validatePackage(activeRoot);
|
|
const activeManifestHash = sha256(path.join(activeRoot, "manifest.json"));
|
|
const documentPath = path.join(caseRoot, "WebBox.FCStd");
|
|
fs.copyFileSync(fcstdSource, documentPath);
|
|
const documentHash = sha256(documentPath);
|
|
testCase.mutation(path.join(candidateRoot, testCase.path));
|
|
let rejected = false;
|
|
let reason = "";
|
|
try { validatePackage(candidateRoot); } catch (error) { rejected = true; reason = error instanceof Error ? error.message : String(error); }
|
|
const activeStillValid = validatePackage(activeRoot).packageVersion === activeManifest.packageVersion;
|
|
const documentStillValid = sha256(documentPath) === documentHash;
|
|
results.push({
|
|
id: testCase.id,
|
|
mutatedFile: testCase.path,
|
|
rejectedBeforeReplacement: rejected,
|
|
reason,
|
|
replacementAttempted: false,
|
|
activePackageUnchanged: activeManifestHash === sha256(path.join(activeRoot, "manifest.json")),
|
|
activePackageValid: activeStillValid,
|
|
documentUnchanged: documentStillValid,
|
|
});
|
|
}
|
|
|
|
const result = {
|
|
schema: "freecad-web-upgrade-corrupt/v1",
|
|
browserPolicy: "Chrome only; no Firefox execution",
|
|
cases: results,
|
|
summary: {
|
|
caseCount: results.length,
|
|
rejectedBeforeReplacement: results.filter((item) => item.rejectedBeforeReplacement).length,
|
|
oldPackagePreserved: results.every((item) => item.activePackageUnchanged && item.activePackageValid),
|
|
documentPreserved: results.every((item) => item.documentUnchanged),
|
|
result: results.length === 3 && results.every((item) => item.rejectedBeforeReplacement && item.activePackageUnchanged && item.activePackageValid && item.documentUnchanged)
|
|
? "pass_all_corruption_cases_rejected_previous_preserved"
|
|
: "fail_corrupt_upgrade_handling",
|
|
},
|
|
};
|
|
fs.writeFileSync(path.join(docsRoot, "freecad-web-upgrade-corrupt.json"), `${JSON.stringify(result, null, 2)}\n`);
|
|
console.log(`corruption cases rejected: ${result.summary.rejectedBeforeReplacement}/${result.summary.caseCount}`);
|
|
console.log(`result=${result.summary.result}`);
|
|
if (result.summary.result.startsWith("fail")) process.exitCode = 1;
|