diff --git a/src/core/xml_parser.js b/src/core/xml_parser.js index 3d3ffb983..95d1614b6 100644 --- a/src/core/xml_parser.js +++ b/src/core/xml_parser.js @@ -53,12 +53,12 @@ class XMLParserBase { } _resolveEntities(s) { - return s.replaceAll(XMLParserBase._entityRegex, (_, hex, dec, entity) => { - if (hex) { - return String.fromCodePoint(parseInt(hex, 16)); - } - if (dec) { - return String.fromCodePoint(parseInt(dec, 10)); + return s.replaceAll(XMLParserBase._entityRegex, (all, hex, dec, entity) => { + if (hex || dec) { + const code = hex ? parseInt(hex, 16) : parseInt(dec, 10); + // An out-of-range or unparsable code point is kept as-is, since + // `String.fromCodePoint` would throw on it. + return code >= 0 && code <= 0x10ffff ? String.fromCodePoint(code) : all; } switch (entity) { case "lt": diff --git a/test/unit/xml_spec.js b/test/unit/xml_spec.js index 14a32e67e..d64f8bccd 100644 --- a/test/unit/xml_spec.js +++ b/test/unit/xml_spec.js @@ -109,6 +109,27 @@ describe("XML", function () { }); }); + describe("character references", function () { + const parseText = xml => + new SimpleXMLParser({}).parseFromString(xml).documentElement.textContent; + + it("should resolve the valid ones", function () { + expect(parseText("AB😂�􏿿")).toEqual( + "AB\u{1F602}\0\u{10FFFF}" + ); + }); + + it("should keep the invalid ones as-is", function () { + // These must not throw: `String.fromCodePoint` rejects anything which + // isn't a code point. + expect(parseText("&#xZZ;")).toEqual("&#xZZ;"); + expect(parseText("&#zz;")).toEqual("&#zz;"); + expect(parseText("")).toEqual("�"); + expect(parseText("")).toEqual("�"); + expect(parseText("&#-1;")).toEqual("&#-1;"); + }); + }); + it("should parse processing instructions", function () { const xml = `