mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
d672cc2399
Decode native binary plists from bounded owned source bytes before conversion can erase scalar values. Share scalar preflight and isolate overlapping decoder records while retaining exact integers, date uncertainty, and original bytes. Refuse numeric payloads that read structural table bytes, and avoid retaining projection metadata when callers only need structural validation. Closes #5701 Closes #5703 Closes #5705 Closes #5782 Closes #5783 Closes #5792
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { execFile } from "node:child_process";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
|
|
import { build } from "plist";
|
|
import { expect, it } from "vitest";
|
|
|
|
import { analyzeInterfaceBuilderBundle } from "../../../src/artifacts/apple/InterfaceBuilderAnalysis.js";
|
|
import { inspectBundleKeyedArchive } from "../../../src/artifacts/apple/KeyedArchiveReader.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const archive = build({
|
|
$archiver: "NSKeyedArchiver",
|
|
$objects: ["$null", "Café"],
|
|
$top: { root: { CF$UID: 1 } },
|
|
});
|
|
|
|
it("preflights repeated unterminated quoted declarations without suffix rescans", async () => {
|
|
const preflightUrl = new URL(
|
|
"../../../dist/artifacts/apple/PropertyListDecodeBudget.js",
|
|
import.meta.url,
|
|
).href;
|
|
const { stdout } = await promisify(execFile)(
|
|
process.execPath,
|
|
[
|
|
"--input-type=module",
|
|
"-e",
|
|
`
|
|
import { preflightPropertyListDecode } from ${JSON.stringify(preflightUrl)};
|
|
const xml = '<!DOCTYPE "x>x" '.repeat(70000);
|
|
const observed = preflightPropertyListDecode(Buffer.from(xml), 16 * 1024 * 1024);
|
|
process.stdout.write(JSON.stringify({ observed, expected: xml.length * 4 }));
|
|
`,
|
|
],
|
|
{ timeout: 5000 },
|
|
);
|
|
const result = JSON.parse(stdout) as {
|
|
observed: { estimatedBytes: number };
|
|
expected: number;
|
|
};
|
|
expect(result.observed.estimatedBytes).toBe(result.expected);
|
|
}, 10000);
|
|
|
|
const fixture = async (declaration: string) => {
|
|
const bundlePath = await createTestTempDirectory("rea-xml-declaration-");
|
|
await writeFile(
|
|
join(bundlePath, "Panel.nib"),
|
|
Buffer.from(archive.replace("UTF-8", declaration), "utf8"),
|
|
);
|
|
return { bundlePath, targetSha256: "a".repeat(64) };
|
|
};
|
|
|
|
it.each(["UTF-8", "utf-8"])(
|
|
"retains UTF-8 characters with the supported declaration %s",
|
|
async (declaration) => {
|
|
const input = await fixture(declaration);
|
|
const archiveResult = await inspectBundleKeyedArchive({
|
|
...input,
|
|
parameters: { path: "Panel.nib" },
|
|
});
|
|
expect(archiveResult.objects).toContainEqual(
|
|
expect.objectContaining({ value: "Café" }),
|
|
);
|
|
const interfaceBuilderResult = await analyzeInterfaceBuilderBundle(input);
|
|
expect(interfaceBuilderResult.documents).toHaveLength(1);
|
|
expect(interfaceBuilderResult.documents[0]?.relative_path).toBe(
|
|
"Panel.nib",
|
|
);
|
|
},
|
|
);
|
|
|
|
it.each(["REA-UNSUPPORTED", "ISO-8859-1"])(
|
|
"reports unsupported keyed archive encoding %s instead of substituting UTF-8 text",
|
|
async (declaration) => {
|
|
const input = await fixture(declaration);
|
|
await expect(
|
|
inspectBundleKeyedArchive({
|
|
...input,
|
|
parameters: { path: "Panel.nib" },
|
|
}),
|
|
).rejects.toMatchObject({
|
|
reason: "format",
|
|
message: expect.stringContaining(declaration),
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each(["REA-UNSUPPORTED", "ISO-8859-1"])(
|
|
"reports unsupported Interface Builder encoding %s without graph substitution",
|
|
async (declaration) => {
|
|
const result = await analyzeInterfaceBuilderBundle(
|
|
await fixture(declaration),
|
|
);
|
|
expect(result.documents).toEqual([]);
|
|
expect(result.graph.nodes).toEqual([]);
|
|
expect(result.graph.coverage).toContainEqual(
|
|
expect.objectContaining({
|
|
facet: "archive_decode",
|
|
status: "partial",
|
|
reason: "one_or_more_archives_invalid",
|
|
}),
|
|
);
|
|
expect(result.graph.truncated).toBe(true);
|
|
},
|
|
);
|