diff --git a/src/lib/Parser.ts b/src/lib/Parser.ts
index 8f4c429..5df9805 100644
--- a/src/lib/Parser.ts
+++ b/src/lib/Parser.ts
@@ -609,7 +609,8 @@ export class Parser {
? parseInt(ref.slice(2), 16) // Hex codepoint.
: parseInt(ref.slice(1), 10); // Decimal codepoint.
- if (isNaN(codePoint)) {
+ if (isNaN(codePoint)
+ || !/^#(?:x[0-9A-Fa-f]+|[0-9]+)$/.test(ref)) {
throw this.error('Invalid character reference');
}
diff --git a/tests/lib/Parser.test.js b/tests/lib/Parser.test.js
index d7f5f6e..0eca1a2 100644
--- a/tests/lib/Parser.test.js
+++ b/tests/lib/Parser.test.js
@@ -408,6 +408,17 @@ describe('Parser', () => {
assert.strictEqual(parseXml('
').root.children[0].text, '\r\n');
});
+ // A numeric character reference must consist solely of digits, so a
+ // reference like `Aa;` or `Ag;` is malformed and must be rejected
+ // rather than silently truncated to the leading numeric portion.
+ // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-CharRef
+ it('rejects a numeric character reference with trailing non-digit characters', () => {
+ assert.throws(() => parseXml('Aa;'), /Invalid character reference/);
+ assert.throws(() => parseXml('0f;'), /Invalid character reference/);
+ assert.throws(() => parseXml('Ag;'), /Invalid character reference/);
+ assert.throws(() => parseXml(''), /Invalid character reference/);
+ });
+
it('handles many character references in a single attribute', () => {
let { root } = parseXml('');
assert.strictEqual(root.attributes.b, "<".repeat(35));