diff --git a/src/core/xml_parser.js b/src/core/xml_parser.js index 95d1614b6..8b30b4e74 100644 --- a/src/core/xml_parser.js +++ b/src/core/xml_parser.js @@ -49,7 +49,8 @@ function isWhitespaceString(s) { class XMLParserBase { static get _entityRegex() { - return shadow(this, "_entityRegex", /&(?:#x([^;]+)|#([^;]+)|([^;]+));/g); + // Entity references cannot contain "&", keeping the scan linear. + return shadow(this, "_entityRegex", /&(?:#x([^;&]+)|#([^;&]+)|([^;&]+));/g); } _resolveEntities(s) { diff --git a/test/unit/xml_spec.js b/test/unit/xml_spec.js index d64f8bccd..352a247e7 100644 --- a/test/unit/xml_spec.js +++ b/test/unit/xml_spec.js @@ -153,4 +153,33 @@ describe("XML", function () { ["foo", ""], ]); }); + + describe("entities", function () { + const parseText = xml => + new SimpleXMLParser({}).parseFromString(xml).documentElement.textContent; + + it("should resolve the entities", function () { + expect( + parseText("<b> & "c" 'd'") + ).toEqual(` & "c" 'd'`); + expect(parseText("AB")).toEqual("AB"); + }); + + it("should keep the unresolved entities as-is", function () { + expect(parseText("&unknown; a&b;c")).toEqual("&unknown; a&b;c"); + }); + + it("should resolve an entity preceded by a bare ampersand", function () { + expect(parseText("AT&T & Co")).toEqual("AT&T & Co"); + expect(parseText("&&")).toEqual("&&"); + }); + + it("should handle a long run of ampersands efficiently", function () { + const text = "&".repeat(100000); + + const startTime = performance.now(); + expect(parseText(`${text}`)).toEqual(text); + expect(performance.now() - startTime).toBeLessThan(1000); + }); + }); });