From 5afd8a850b2ebd62e3400a5e2ece1a1cfa98bab6 Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:12:59 +0000 Subject: [PATCH] fix(plist): keep character references beyond Unicode literal Reserving the __proto__ placeholder decodes the whole document, so or a comment with an out-of-range reference threw a RangeError, and every XML plist reader failed on a valid document. References beyond U+10FFFF now stay as written. The XML decoder still rejects them outside comments and CDATA. Co-Authored-By: Claude Opus 5.5 --- src/domain/propertyListKeys.test.ts | 10 ++++++++++ src/domain/propertyListKeys.ts | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/domain/propertyListKeys.test.ts b/src/domain/propertyListKeys.test.ts index 759c3e454..e15fcb27e 100644 --- a/src/domain/propertyListKeys.test.ts +++ b/src/domain/propertyListKeys.test.ts @@ -49,6 +49,16 @@ it("keeps an entity-encoded key that decodes to the placeholder", () => { }); }); +it("keeps entity-like text beyond Unicode in comments and CDATA", () => { + const text = plist( + "raw__proto__x", + ); + expect(parseXmlPropertyList(text)).toEqual({ + value: { raw: "�" }, + omittedPrototypeKeys: 1, + }); +}); + it("omits own __proto__ entries that JSON decoding creates", () => { const decoded: unknown = JSON.parse( '{"__proto__":{"x":1},"a":[{"__proto__":2,"b":3}]}', diff --git a/src/domain/propertyListKeys.ts b/src/domain/propertyListKeys.ts index c2192f00b..0007e1e5e 100644 --- a/src/domain/propertyListKeys.ts +++ b/src/domain/propertyListKeys.ts @@ -56,13 +56,23 @@ const XML_ENTITIES: Readonly> = { quot: '"', }; +/** + * Expand XML character and predefined entity references. A reference beyond + * Unicode stays as written: comments and CDATA may hold such text literally, + * and the XML decoder rejects it anywhere else. + */ const decodeXmlText = (text: string): string => text.replace( /&(?:#x([\da-f]+)|#(\d+)|([a-z]+));/giu, (entity, hex?: string, decimal?: string, name?: string) => { - if (hex !== undefined) return String.fromCodePoint(parseInt(hex, 16)); - if (decimal !== undefined) - return String.fromCodePoint(parseInt(decimal, 10)); + const codePoint = + hex !== undefined + ? parseInt(hex, 16) + : decimal !== undefined + ? parseInt(decimal, 10) + : undefined; + if (codePoint !== undefined) + return codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : entity; return (name === undefined ? undefined : XML_ENTITIES[name]) ?? entity; }, ); @@ -77,6 +87,7 @@ export const parseXmlPropertyList = ( ): PropertyListWithoutPrototypeKeys => { // The decoded text holds every key the decoder can produce, including // entity-encoded spellings of the placeholder, so no source key aliases it. + // Decoding comment or CDATA text too can only lengthen the placeholder. const decoded = decodeXmlText(text); let placeholder = "__rea_prototype_key__"; while (decoded.includes(placeholder)) placeholder = `_${placeholder}`;