Merge pull request #870 from morluto/fix/keyed-archive-uid-named-keys

fix(artifacts): read keyed archive dictionaries keyed UID as dictionaries
This commit is contained in:
Kaoru0822 authored and GitHub committed 2026-10-06 23:03:09 -07:00
commit cfd2b0c75d
4 files changed
+87 -11

No files matched your search

+35
View File
@@ -51,6 +51,41 @@ describe("inert keyed archive decoding", () => {
expect.objectContaining({ source: 1, target: 2, status: "resolved" }),
);
});
it.each([
["binary", { UID: 1 }],
["XML", { CF$UID: 1 }],
] as const)(
"resolves a %s root encoded under a UID-named key",
(format, reference) => {
const named = {
...archive,
$objects: ["$null", "payload"],
$top: { UID: reference, CF$UID: reference },
};
const graph = decodeKeyedArchiveBytes(
Buffer.from(format === "binary" ? buildBinary(named) : build(named)),
{ offset: 0, limit: 2 },
);
expect(
graph.references.map(({ source, path, target, status }) => ({
source,
path,
target,
status,
})),
).toEqual([
{ source: null, path: ["CF$UID"], target: 1, status: "resolved" },
{ source: null, path: ["UID"], target: 1, status: "resolved" },
]);
const selected = decodeKeyedArchiveBytes(
Buffer.from(format === "binary" ? buildBinary(named) : build(named)),
{ root: "UID", offset: 0, limit: 2 },
);
expect(selected.references).toEqual([
expect.objectContaining({ path: ["UID"], target: 1 }),
]);
},
);
it("rejects missing roots, malformed plist, and non-keyed archives", () => {
expect(() =>
decodeKeyedArchiveBytes(Buffer.from("bplist00bad"), {
+11 -10
View File
@@ -54,11 +54,17 @@ const record = (
value !== null && typeof value === "object" && !Array.isArray(value)
? value
: undefined;
const uid = (value: JsonValue | undefined): JsonValue | undefined => {
/**
* A UID decodes as a one-key `UID` (binary) or `CF$UID` (XML) dictionary with
* a numeric value. Any other value means an ordinary dictionary, such as a
* `$top` that holds a root encoded under the key `UID`.
*/
const uid = (value: JsonValue | undefined): number | undefined => {
const object = record(value);
if (object === undefined || Object.keys(object).length !== 1)
return undefined;
return Object.hasOwn(object, "UID") ? object.UID : object["CF$UID"];
const marker = Object.hasOwn(object, "UID") ? object.UID : object["CF$UID"];
return typeof marker === "number" ? marker : undefined;
};
/** Interpret serialized Foundation references without instantiating archive classes. */
@@ -99,11 +105,8 @@ export const projectKeyedArchive = (
);
const target = uid(item.value);
if (target !== undefined) {
const valid =
typeof target === "number" &&
Number.isSafeInteger(target) &&
target >= 0;
const index = valid ? target : null;
const index =
Number.isSafeInteger(target) && target >= 0 ? target : null;
references.push({
source,
path: item.path,
@@ -143,9 +146,7 @@ export const projectKeyedArchive = (
const fields = record(value);
const classUid = uid(fields?.$class);
const classId =
typeof classUid === "number" &&
Number.isSafeInteger(classUid) &&
classUid >= 0
classUid !== undefined && Number.isSafeInteger(classUid) && classUid >= 0
? classUid
: null;
const descriptor = classId === null ? undefined : record(table[classId]);