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);
+ });
+ });
});