fix(desktop): normalize XML attribute whitespace and line endings during seed scrubbing (#9099)

This commit is contained in:
highmachs authored and GitHub committed 2026-11-06 14:25:26 +08:00
1 parent 8580f1430c
commit 15fad0efa3
2 files changed
+46 -3

No files matched your search

+38
View File
@@ -210,6 +210,44 @@ describe("scrubSeed on JSON and XML", () => {
});
});
describe("scrubSeed XML whitespace normalization", () => {
it("normalizes literal whitespace in rewritten XML attributes without inventing character references", () => {
const xml = '<root path="/home/ada/a\n b" other="/home/ada/a\t b"/>';
const outcome = scrubbed(xml, "xml");
expect(outcome.text).toBe(
`<root path="${SEED_HOME}/a b" other="${SEED_HOME}/a b"/>`,
);
expect(outcome.holdsIdentity).toBe(false);
});
it("normalizes literal line endings in rewritten XML character data without inventing CR references", () => {
const crlf = `<root>/home/ada/a\r\n b</root>`;
const cr = `<root>/home/ada/a\r b</root>`;
expect(scrubbed(crlf, "xml").text).toBe(`<root>${SEED_HOME}/a\n b</root>`);
expect(scrubbed(cr, "xml").text).toBe(`<root>${SEED_HOME}/a\n b</root>`);
});
it("preserves explicit character references in XML attributes and character data", () => {
const xml = `<root path="/home/ada/a&#10; b" tab="/home/ada/a&#9; b">/home/ada/a&#13; b</root>`;
const outcome = scrubbed(xml, "xml");
expect(outcome.text).toBe(
`<root path="${SEED_HOME}/a&#10; b" tab="${SEED_HOME}/a&#9; b">${SEED_HOME}/a&#13; b</root>`,
);
expect(outcome.holdsIdentity).toBe(false);
});
it("leaves XML with literal line endings or whitespace byte-for-byte unchanged when no identity matches", () => {
const xml = '<root path="/unmatched/path/a\n b">\r\nclean\r\n</root>';
const outcome = scrubSeed(Buffer.from(xml), seed("xml"), GEANY);
expect(outcome.bytes).toBeUndefined();
expect(outcome.holdsIdentity).toBe(false);
});
});
describe("scrubSeed on bytes it does not rewrite", () => {
it("reports a seed whose shipped bytes still hold the host name or the captured home", () => {
const database = Buffer.concat([
+8 -3
View File
@@ -95,8 +95,12 @@ const XML_ENTITIES: Readonly<Record<string, string>> = {
const referenced = (whole: string, point: number): string =>
point <= 0x10ffff ? String.fromCodePoint(point) : whole;
const decodeXml = (text: string): string =>
text.replace(
const decodeXml = (text: string, quote?: string): string => {
const normalized =
quote !== undefined
? text.replace(/\r\n|[\r\n\t]/gu, " ")
: text.replace(/\r\n|\r/gu, "\n");
return normalized.replace(
XML_REFERENCE,
(whole, hex?: string, decimal?: string, name?: string) =>
hex !== undefined
@@ -105,6 +109,7 @@ const decodeXml = (text: string): string =>
? referenced(whole, Number(decimal))
: (XML_ENTITIES[name ?? ""] ?? whole),
);
};
/** Character data, or an attribute value in the given quote, escaped so XML reads it back as written. */
const encodeXml = (text: string, quote: string | undefined): string =>
@@ -127,7 +132,7 @@ const encodeXml = (text: string, quote: string | undefined): string =>
*/
const scrubXml = (text: string, identity: HostIdentity): TextScrub => {
const data = (raw: string, quote?: string) => {
const value = decodeXml(raw);
const value = decodeXml(raw, quote);
const clean = scrubHostIdentity(value, identity);
return clean === value ? raw : encodeXml(clean, quote);
};