fix(native): normalize otool line endings and strip plist JSON BOM (#533)
This commit is contained in:
parent
0f77ada55e
commit
3e4d4d93fd
|
|
@ -44,8 +44,19 @@ interface ParsedLoadCommands {
|
|||
readonly flags: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* otool emits LF, but a capture replayed from another host or toolchain can
|
||||
* carry CRLF. The section/command boundaries below match a bare `\n`, so a
|
||||
* stray `\r` would swallow the terminator and silently empty `sections` while
|
||||
* leaking section fields into the command. Normalizing once at the boundary
|
||||
* keeps every parser below working on one line ending.
|
||||
*/
|
||||
const normalizeLineEndings = (output: string): string =>
|
||||
output.replaceAll("\r\n", "\n");
|
||||
|
||||
/** Parse stable fields from `otool -l`, preserving unknown command fields. */
|
||||
export const parseOtoolLoadCommands = (output: string) => {
|
||||
export const parseOtoolLoadCommands = (raw: string) => {
|
||||
const output = normalizeLineEndings(raw);
|
||||
const headerTokens = parseHeaderTokens(output);
|
||||
const state = createLoadCommandState(headerTokens);
|
||||
for (const block of output.split(/(?=Load command \d+)/u))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ const plistObject = z.record(z.string(), z.unknown());
|
|||
|
||||
/** Parse plutil JSON output and project stable bundle metadata. */
|
||||
export const parsePlistJson = (output: string) => {
|
||||
const value: unknown = JSON.parse(output);
|
||||
// plutil output can carry a UTF-8 BOM, which JSON.parse rejects outright.
|
||||
const value: unknown = JSON.parse(output.replace(/^\uFEFF/u, ""));
|
||||
const object = plistObject.safeParse(value);
|
||||
const field = (name: string): string | null => {
|
||||
if (!object.success) return null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { expect, it } from "vitest";
|
||||
|
||||
import { parseOtoolLoadCommands } from "../../../../src/native/parsers/otool.js";
|
||||
import { parsePlistJson } from "../../../../src/native/parsers/plist.js";
|
||||
import { nativeFixture } from "../../../fixtures/nativeCommands.js";
|
||||
|
||||
const toCrlf = (value: string): string => value.replaceAll("\n", "\r\n");
|
||||
|
||||
it("keeps section blocks separate under CRLF captures", async () => {
|
||||
const lf = parseOtoolLoadCommands(await nativeFixture("otool-load.txt"));
|
||||
const crlf = parseOtoolLoadCommands(
|
||||
toCrlf(await nativeFixture("otool-load.txt")),
|
||||
);
|
||||
expect(crlf.segments).toMatchObject(lf.segments);
|
||||
const crlfText = crlf.segments.find(({ name }) => name === "__TEXT");
|
||||
expect(crlfText?.sections.length).toBeGreaterThan(0);
|
||||
expect(crlfText?.sections.map(({ name }) => name)).toContain("__text");
|
||||
});
|
||||
|
||||
it("does not leak section fields into command fields under CRLF captures", async () => {
|
||||
const crlf = parseOtoolLoadCommands(
|
||||
toCrlf(await nativeFixture("otool-load.txt")),
|
||||
);
|
||||
const segment = crlf.commands.find(
|
||||
(command) => command.kind === "LC_SEGMENT_64",
|
||||
);
|
||||
expect(segment?.fields.sectname).toBeUndefined();
|
||||
expect(segment?.fields["time stamp"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("accepts a byte-order mark before plist JSON", () => {
|
||||
const parsed = parsePlistJson(
|
||||
'\uFEFF{"CFBundleIdentifier":"com.owned.fixture"}',
|
||||
);
|
||||
expect(parsed.bundle.identifier).toBe("com.owned.fixture");
|
||||
});
|
||||
Loading…
Reference in New Issue