// Verify the two backported guards behave correctly against the PoC payload.
//
// NOTE: Guards A and B below re-declare the backported logic so it can be
// exercised in isolation - the shipped code lives in a minified vendor bundle
// that cannot be imported directly. To catch the bundle silently drifting away
// from these reproductions, the "source tripwire" section at the end reads the
// actual pdf.js / pdf.worker.js files and fails if the guards disappear.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..", "..");

let failures = 0;
function check(name, condition) {
  const ok = condition === true;
  console.log((ok ? "PASS" : "FAIL"), name, "->", condition);
  if (!ok) {
    failures++;
  }
}

const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];

// ---- Guard A: evaluator FontMatrix type-check (as backported) ----
function sanitizeFontMatrix(raw) {
  let fontMatrix = raw;
  if (!Array.isArray(fontMatrix) || fontMatrix.length !== 6 ||
      fontMatrix.some(x => typeof x !== "number")) {
    fontMatrix = FONT_IDENTITY_MATRIX;
  }
  return fontMatrix;
}

// The PoC's /FontMatrix parses to 6 elements where the last is the attacker
// string. Simulate what dict.getArray("FontMatrix") yields:
const maliciousPayload = "1);\nglobalThis.__cve_2024_4367__='reached';\n//";
const attackerMatrix = [0.1, 0, 0, 0.1, 0, maliciousPayload]; // 6 elems, one non-number
const benignMatrix = [0.001, 0, 0, 0.001, 0, 0];

check("A1 malicious rejected",
  JSON.stringify(sanitizeFontMatrix(attackerMatrix)) === JSON.stringify(FONT_IDENTITY_MATRIX));
check("A2 benign preserved",
  JSON.stringify(sanitizeFontMatrix(benignMatrix)) === JSON.stringify(benignMatrix));
check("A3 wrong-length rejected",
  JSON.stringify(sanitizeFontMatrix([1,2,3])) === JSON.stringify(FONT_IDENTITY_MATRIX));

// ---- Guard B: sink numeric coercion (as backported) ----
function buildBody(cmds) {
  const jsBuf = [];
  for (const current of cmds) {
    let args = "";
    if (current.args !== undefined) {
      if (current.cmd === "scale") {
        args = current.args.join(",");
      } else {
        args = current.args.map(a => (typeof a === "number" ? a : 0)).join(",");
      }
    }
    jsBuf.push("c.", current.cmd, "(", args, ");\n");
  }
  return jsBuf.join("");
}

// If guard A somehow failed, the sink still gets the attacker string as a transform arg.
const cmdsIfGuardAFailed = [
  { cmd: "save" },
  { cmd: "transform", args: [0.1,0,0,0.1,0, maliciousPayload] },
  { cmd: "scale", args: ["size","-size"] },
];
const body = buildBody(cmdsIfGuardAFailed);
check("B1 no payload in body", !body.includes("__cve_2024_4367__"));
check("B2 scale identifiers kept", body.includes("c.scale(size,-size)"));
// Ensure the generated body is still valid JS and runs harmlessly
globalThis.__cve_2024_4367__ = undefined;
const fn = new Function("c","size", body);
const calls = [];
const ctx = new Proxy({}, { get: () => (...a)=>calls.push(a) });
fn(ctx, 10);
check("B3 body executed w/o injection", globalThis.__cve_2024_4367__ === undefined);
check("B4 transform got numeric 0 for bad arg",
  JSON.stringify(calls.find(c=>c.length===6)) === JSON.stringify([0.1,0,0,0.1,0,0]));

// ---- Source tripwire: the guards must still be present in the shipped bundle ----
// These are coarse substring checks. They cannot prove the guard is wired up
// correctly, but they fail loudly if the bundle is regenerated/upgraded without
// carrying the backport forward - which is the realistic regression path.
const workerSrc = readFileSync(join(repoRoot, "js/vendor/pdfjs/build/pdf.worker.js"), "utf8");
const pdfSrc = readFileSync(join(repoRoot, "js/vendor/pdfjs/build/pdf.js"), "utf8");

check("S1 worker FontMatrix length guard present",
  workerSrc.includes("fontMatrix.length !== 6"));
check("S2 worker FontMatrix type guard present",
  /fontMatrix\.some\(function \(x\) \{\s*return typeof x !== "number";/.test(workerSrc));
check("S3 pdf.js glyph-arg numeric coercion present",
  /typeof arg === "number" \? arg : 0/.test(pdfSrc));
check("S4 pdf.js preserves intentional scale identifiers",
  pdfSrc.includes('current.cmd === "scale"'));

if (failures > 0) {
  console.error(`\n${failures} check(s) failed`);
  process.exit(1);
}
console.log("\nAll checks passed");
