CVE-2021-21366 (JavaScript, published 2021-03-12T17:15Z)
Description: value: xmldom is a pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module. xmldom versions 0.4.0 and older do not correctly preserve system identifiers, FPIs or namespaces when repeatedly parsing and serializing maliciously crafted documents. This may lead to unexpected syntactic changes during XML processing in some downstream applications. This is fixed in version 0.5.0. As a workaround downstream applications can validate the input and reject the maliciously crafted documents.
Severity: MEDIUM (CVSS v3 base score: 4.3)
Weakness: CWE-436 \u2014 Interpretation Conflict
Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state.
Repository: https://github.com/xmldom/xmldom
Files changed:
lib/dom-parser.js
lib/dom.js
lib/sax.js
test/error/__snapshots__/reported-levels.test.js.snap
test/error/__snapshots__/xml-error.test.js.snap
test/error/error-handler.test.js
test/error/reported-levels.test.js
test/error/reported.js
test/error/xml-reader-dom-handler-errors.test.js
test/html/__snapshots__/normalize.test.js.snap
test/parse/doctype.test.js
test/parse/parse-error.test.js
test/xmltest/__snapshots__/not-wf.test.js.snap
Commit message: Merge pull request from GHSA-h6q6-9hqw-rwfv

* fix!: Preserve quotes in DOCTYPE declaration

Since the only purpose of parsing the DOCTYPE is to be able to restore it when serializing,
we decided that it would be best to leave the parsed publicId and systemId as is,
including any quotes.

BREAKING CHANGE: If somebody relies on the actual unquoted values of those ids,
they will need to take care of either single or double quotes and the right escaping.

(Without this change this would not have been possible because the SAX parser already
dropped the information about the quotes that have been used in the source.)

https://www.w3.org/TR/2006/REC-xml11-20060816/#dtd
https://www.w3.org/TR/2006/REC-xml11-20060816/#IDAX1KS (External Entity Declaration)

Co-authored-by: Christian Bewernitz <coder@karfau.de>
Co-authored-by: Chris Brody <chris.brody+brodybits@gmail.com>

* feat(security): Improve error reporting; throw on duplicate attribute

BREAKING CHANGE: It is currently not clear how to consistently deal with duplicate attributes,
so it is also safer for our users to fail when detecting them.

It is possible to configure the `DOMParser.errorHandler` before parsing, to handle those errors differently.

To accomplish this and also be able to verify it in tests we needed to:

- create a new `Error` type `ParseError` and export it
- Throw `ParseError` from `errorHandler.fatalError` and prevent those from being caught in `XMLReader`.
- export `DOMHandler` constructor as `__DOMHandler`

Co-authored-by: Christian Bewernitz <coder@karfau.de>
Co-authored-by: Chris Brody <chris.brody+brodybits@gmail.com>

Co-authored-by: Christian Bewernitz <coder@karfau.de>

Vulnerable code:
		console.error('[xmldom fatalError]\t'+error,_locator(this.locator));
	    throw error;
var XMLReader = require('./sax').XMLReader;
			buf.push(' PUBLIC "',pubid);
				buf.push( '" "',sysid);
			buf.push('">');
			buf.push(' SYSTEM "',sysid,'">');
		            	errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName );
			//errorHandler.error('element parse error: '+e);
			//throw e;
				throw new Error('attribute equal must after attrName');
					el.add(attrName,value,start-1);
				el.add(attrName,value,start);
				throw new Error('attribute value must after "="');
				throw new Error("attribute invalid close char('/')")
			//throw new Error('unexpected end of input')
					errorHandler.warning('attribute "'+value+'" missed quot(")!!');
					el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start)
					el.add(value,value,start)
					el.add(attrName,value,start)
					el.add(attrName,attrName,start);
			domBuilder.startDTD(name,pubid && pubid.replace(/^(['"])(.*?)\1$/,'$2'),
					sysid && sysid.replace(/^(['"])(.*?)\1$/,'$2'));
/**
 * @param source
 */
function ElementAttributes(source){
	
	add:function(qName,value,offset){

    "[xmldom warning]	attribute \\"1\\" missed quot(\\")!!
exports[`html vs xml: unclosed document in text/xml 1`] = `
Object {
  "actual": "<img/>",
  "warning": Array [
    "[xmldom warning]	unclosed xml attribute
@#[line:1,col:1]",
  ],
}
`;

exports[`html vs xml: unclosed document in text/html 1`] = `
Object {
  "actual": "<img xmlns=\\"http://www.w3.org/1999/xhtml\\"/>",
}
`;
'use strict'

const { DOMParser } = require('../../lib/dom-parser')

const XML_ERROR = '<html><body title="1<2"><table&lt;;test</body></body></html>'
const XML_ERROR_AND_WARNING = '<html disabled><1 1="2"/></body></html>'

describe('errorHandler', () => {
	it('only single function with two args builds map', () => {
		const errors = {}
		const parser = new DOMParser({
			errorHandler: function (key, msg) {
				errors[key] = msg
			},
		})

		parser.parseFromString(XML_ERROR_AND_WARNING, 'text/xml')

		expect(errors).toMatchObject({
			warning: expect.stringMatching(/.*/),
			error: expect.stringMatching(/.*/),
		})
	})

	it('only one function with one argument builds list', () => {
		const errors = []
		const parser = new DOMParser({
			errorHandler: function (msg) {
				errors.push(msg)
			},
		})

		parser.parseFromString(XML_ERROR_AND_WARNING, 'text/xml')

		expect(errors).toMatchObject([/\[xmldom warning]/, /\[xmldom error]/])
	})

	it.each(['warning', 'error'])(
		'errorHandler for only one level: %s',
		(level) => {
			const errors = []
			const parser = new DOMParser({
				errorHandler: {
					[level]: function (msg) {
						errors.push(msg)
					},
				},
			})

			parser.parseFromString(XML_ERROR_AND_WARNING, 'text/xml')

			expect(errors).toHaveLength(1)
		}
	)
	it.todo(
		'errorHandler for only one level: fatalError'
		/*
		I was not able to create a test case for a fatalError.
		It might need to be removed from the API, since all but one cases are in comments
		and an error is thrown instead. The one case left I was not able to reproduce.
		*/
	)

	it('error function throwing is not caught', () => {
		const errors = []
		const ERROR_MSG = 'FROM TEST'

		const parser = new DOMParser({
			locator: {}, // removing the locator makes the test fail!
			errorHandler: {
				error: function (msg) {
					errors.push(msg)
					throw new Error(ERROR_MSG)
				},
			},
		})

		expect(() => {
			parser.parseFromString(XML_ERROR, 'text/html')
		}).toThrow(ERROR_MSG)
		expect(errors).toMatchObject([/\n@#\[line\:\d+,col\:\d+\]/])
	})
})
    "[xmldom warning]	attribute \\"123&&456\\" missed quot(\\")!!
    "[xmldom warning]	attribute \\"1\\" missed quot(\\")!!
    "[xmldom warning]	attribute \\"1/2\\" missed quot(\\")!!
    "[xmldom warning]	attribute \\"1\\" missed quot(\\")!!
    "[xmldom warning]	attribute \\"v1\\" missed quot(\\")!!

Fixed code:
		throw new ParseError(error, this.locator);
var sax = require('./sax');
var XMLReader = sax.XMLReader;
var ParseError = sax.ParseError;
exports.__DOMHandler = DOMHandler;
			buf.push(' PUBLIC ', pubid);
				buf.push(' ', sysid);
			buf.push('>');
			buf.push(' SYSTEM ', sysid, '>');
/**
 * Creates an error that will not be caught by XMLReader aka the SAX parser.
 *
 * @param {string} message
 * @param {any?} locator Optional, can provide details about the location in the source
 * @constructor
 */
function ParseError(message, locator) {
	this.message = message
	this.locator = locator
	if(Error.captureStackTrace) Error.captureStackTrace(this, ParseError);
}
ParseError.prototype = new Error();
ParseError.prototype.name = ParseError.name

		            	errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName ); // No known test case
			if (e instanceof ParseError) {
				throw e;
			}

	/**
	 * @param {string} qname
	 * @param {string} value
	 * @param {number} startIndex
	 */
	function addAttribute(qname, value, startIndex) {
		if (qname in el.attributeNames) errorHandler.fatalError('Attribute ' + qname + ' redefined')
		el.addValue(qname, value, startIndex)
	}
				throw new Error('attribute equal must after attrName'); // No known test case
					addAttribute(attrName, value, start-1);
				addAttribute(attrName, value, start);
				throw new Error('attribute value must after "="'); // No known test case
				throw new Error("attribute invalid close char('/')") // No known test case
					errorHandler.warning('attribute "'+value+'" missed quot(")!');
					addAttribute(attrName, value.replace(/&#?\w+;/g,entityReplacer), start)
					addAttribute(value, value, start)
					addAttribute(attrName, value, start)
					addAttribute(attrName, attrName, start);
			domBuilder.startDTD(name, pubid, sysid);
function ElementAttributes(){
	this.attributeNames = {}
	addValue:function(qName, value, offset) {
		this.attributeNames[qName] = this.length;
exports.ParseError = ParseError;
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SYNTAX_AttributeEqualMissingValue with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value missed!!
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_AttributeEqualMissingValue with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value missed!!||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_AttributeEqualMissingValue with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value missed!!
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_AttributeEqualMissingValue with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value missed!!||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_AttributeMissingEndingQuote with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value no end '\\"' match
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_AttributeMissingEndingQuote with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value no end '\\"' match||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_AttributeMissingEndingQuote with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value no end '\\"' match
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_AttributeMissingEndingQuote with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: attribute value no end '\\"' match||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_ElementClosingNotConnected with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: elements closed character '/' and '>' must be connected to
@#[line:1,col:6]",
]
`;

exports[`SYNTAX_ElementClosingNotConnected with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: elements closed character '/' and '>' must be connected to||@#[line:1,col:6]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_ElementClosingNotConnected with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: elements closed character '/' and '>' must be connected to
@#[line:1,col:6]",
]
`;

exports[`SYNTAX_ElementClosingNotConnected with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: elements closed character '/' and '>' must be connected to||@#[line:1,col:6]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_EndTagMaybeNotComplete with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	end tag name: inner maybe not complete
@#[line:1,col:6]",
]
`;

exports[`SYNTAX_EndTagMaybeNotComplete with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	end tag name: inner maybe not complete||@#[line:1,col:6]
    at parse (lib/sax.js:128)",
  "[xmldom error]	element parse error: Error: [xmldom error]	end tag name: inner maybe not complete||@#[line:1,col:6]||@#[line:1,col:6]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_EndTagMaybeNotComplete with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	end tag name: inner maybe not complete
@#[line:1,col:6]",
]
`;

exports[`SYNTAX_EndTagMaybeNotComplete with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	end tag name: inner maybe not complete||@#[line:1,col:6]
    at parse (lib/sax.js:128)",
  "[xmldom error]	element parse error: Error: [xmldom error]	end tag name: inner maybe not complete||@#[line:1,col:6]||@#[line:1,col:6]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_EndTagNotComplete with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	end tag name: xml is not complete:xml
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_EndTagNotComplete with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	end tag name: xml is not complete:xml||@#[line:1,col:1]
    at parse (lib/sax.js:124)",
  "[xmldom error]	element parse error: Error: [xmldom error]	end tag name: xml is not complete:xml||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_EndTagNotComplete with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	end tag name: xml is not complete:xml
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_EndTagNotComplete with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	end tag name: xml is not complete:xml||@#[line:1,col:1]
    at parse (lib/sax.js:124)",
  "[xmldom error]	element parse error: Error: [xmldom error]	end tag name: xml is not complete:xml||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_InvalidAttributeName with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid attribute:123
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_InvalidAttributeName with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid attribute:123||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_InvalidAttributeName with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid attribute:123
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_InvalidAttributeName with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid attribute:123||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_InvalidTagName with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid tagName:123
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_InvalidTagName with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid tagName:123||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_InvalidTagName with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid tagName:123
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_InvalidTagName with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	element parse error: Error: invalid tagName:123||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_UnclosedComment with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	Unclosed comment
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_UnclosedComment with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	Unclosed comment||@#[line:1,col:1]
    at parseDCC (lib/sax.js:538)",
  "[xmldom error]	element parse error: Error: [xmldom error]	Unclosed comment||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_UnclosedComment with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	Unclosed comment
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_UnclosedComment with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	Unclosed comment||@#[line:1,col:1]
    at parseDCC (lib/sax.js:538)",
  "[xmldom error]	element parse error: Error: [xmldom error]	Unclosed comment||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_UnexpectedEndOfInput with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	unexpected end of input
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_UnexpectedEndOfInput with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	unexpected end of input||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:308)",
  "[xmldom error]	element parse error: Error: [xmldom error]	unexpected end of input||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`SYNTAX_UnexpectedEndOfInput with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	unexpected end of input
@#[line:1,col:1]",
]
`;

exports[`SYNTAX_UnexpectedEndOfInput with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	unexpected end of input||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:308)",
  "[xmldom error]	element parse error: Error: [xmldom error]	unexpected end of input||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`WF_AttributeMissingQuote with mimeType text/html should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"value\\" missed quot(\\")!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingQuote with mimeType text/html should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"value\\" missed quot(\\")!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:333)",
]
`;

exports[`WF_AttributeMissingQuote with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"value\\" missed quot(\\")!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingQuote with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"value\\" missed quot(\\")!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:333)",
]
`;

exports[`WF_AttributeMissingQuote2 with mimeType text/html should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"&\\" missed quot(\\")!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingQuote2 with mimeType text/html should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"&\\" missed quot(\\")!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:363)",
]
`;

exports[`WF_AttributeMissingQuote2 with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"&\\" missed quot(\\")!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingQuote2 with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"&\\" missed quot(\\")!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:363)",
]
`;

exports[`WF_AttributeMissingStartingQuote with mimeType text/html should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed start quot(\\")!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingStartingQuote with mimeType text/html should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed start quot(\\")!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:281)",
]
`;

exports[`WF_AttributeMissingStartingQuote with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed start quot(\\")!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingStartingQuote with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed start quot(\\")!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:281)",
]
`;

exports[`WF_AttributeMissingValue with mimeType text/html should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingValue with mimeType text/html should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:337)",
]
`;

exports[`WF_AttributeMissingValue with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingValue with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:337)",
]
`;

exports[`WF_AttributeMissingValue2 with mimeType text/html should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead2!!
@#[line:1,col:1]",
  "[xmldom warning]	attribute \\"attr2\\" missed value!! \\"attr2\\" instead!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingValue2 with mimeType text/html should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead2!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:385)",
]
`;

exports[`WF_AttributeMissingValue2 with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead2!!
@#[line:1,col:1]",
  "[xmldom warning]	attribute \\"attr2\\" missed value!! \\"attr2\\" instead!!
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeMissingValue2 with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute \\"attr\\" missed value!! \\"attr\\" instead2!!||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:385)",
]
`;

exports[`WF_AttributeValueMustAfterEqual with mimeType text/html should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute value must after \\"=\\"
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeValueMustAfterEqual with mimeType text/html should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute value must after \\"=\\"||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:263)",
]
`;

exports[`WF_AttributeValueMustAfterEqual with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	attribute value must after \\"=\\"
@#[line:1,col:1]",
]
`;

exports[`WF_AttributeValueMustAfterEqual with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	attribute value must after \\"=\\"||@#[line:1,col:1]
    at parseElementStartPart (lib/sax.js:263)",
]
`;

exports[`WF_DuplicateAttribute with mimeType text/html should be reported as fatalError 1`] = `
Array [
  "[xmldom fatalError]	Attribute a redefined
@#[line:1,col:1]",
]
`;

exports[`WF_DuplicateAttribute with mimeType text/xml should be reported as fatalError 1`] = `
Array [
  "[xmldom fatalError]	Attribute a redefined
@#[line:1,col:1]",
]
`;

exports[`WF_EntityDeclared with mimeType text/html should be reported as error 1`] = `
Array [
  "[xmldom error]	entity not found:&e;
@#[line:1,col:1]",
]
`;

exports[`WF_EntityDeclared with mimeType text/html should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	entity not found:&e;||@#[line:1,col:1]
    at replace (lib/sax.js:71)",
  "[xmldom error]	element parse error: Error: [xmldom error]	entity not found:&e;||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`WF_EntityDeclared with mimeType text/xml should be reported as error 1`] = `
Array [
  "[xmldom error]	entity not found:&e;
@#[line:1,col:1]",
]
`;

exports[`WF_EntityDeclared with mimeType text/xml should not catch Error thrown in errorHandler.error 1`] = `
Array [
  "[xmldom error]	entity not found:&e;||@#[line:1,col:1]
    at replace (lib/sax.js:71)",
  "[xmldom error]	element parse error: Error: [xmldom error]	entity not found:&e;||@#[line:1,col:1]||@#[line:1,col:1]
    at parse (lib/sax.js:208)",
]
`;

exports[`WF_UnclosedXmlAttribute with mimeType text/xml should be reported as warning 1`] = `
Array [
  "[xmldom warning]	unclosed xml attribute
@#[line:1,col:1]",
]
`;

exports[`WF_UnclosedXmlAttribute with mimeType text/xml should escalate Error thrown in errorHandler.warning to errorHandler.error 1`] = `
Array [
  "[xmldom warning]	unclosed xml attribute||@#[line:1,col:1]
    at parse (lib/sax.js:173)",
]
`;
    "[xmldom warning]	attribute \\"1\\" missed quot(\\")!

exports[`html vs xml: unclosed document in text/html 1`] = `
Object {
  "actual": "<img xmlns=\\"http://www.w3.org/1999/xhtml\\"/>",
}
`;

exports[`html vs xml: unclosed document in text/xml 1`] = `
Object {
  "actual": "<img/>",
  "warning": Array [
    "[xmldom warning]	unclosed xml attribute
@#[line:1,col:1]",
  ],
}
`;
'use strict'

const { DOMParser } = require('../../lib/dom-parser')
const { REPORTED } = require('./reported')

describe('custom errorHandler', () => {
	it('function with two args receives key and message', () => {
		const errors = {}
		const parser = new DOMParser({
			// currently needs to be a `function` to make the test work,
			// `jest.fn()` or using `() => {}` doesn't work
			errorHandler: function (key, msg) {
				errors[key] = msg
			},
		})

		parser.parseFromString(REPORTED.WF_AttributeMissingQuote.source, 'text/xml')
		expect(errors).toHaveProperty('warning')
		parser.parseFromString(
			REPORTED.SYNTAX_AttributeEqualMissingValue.source,
			'text/xml'
		)
		expect(errors).toHaveProperty('error')
		parser.parseFromString(REPORTED.WF_DuplicateAttribute.source, 'text/xml')
		expect(errors).toHaveProperty('fatalError')
	})

	it('function with one argument builds list', () => {
		const errors = []
		const parser = new DOMParser({
			// currently needs to be a `function` to make the test work,
			// `jest.fn()` or using `() => {}` doesn't work
			errorHandler: function (msg) {
				errors.push(msg)
			},
		})

		parser.parseFromString(REPORTED.WF_AttributeMissingQuote.source, 'text/xml')
		parser.parseFromString(
			REPORTED.SYNTAX_AttributeEqualMissingValue.source,
			'text/xml'
		)
		parser.parseFromString(REPORTED.WF_DuplicateAttribute.source, 'text/xml')

		expect(errors).toMatchObject([
			/\[xmldom warning]/,
			/\[xmldom error]/,
			/\[xmldom fatalError]/,
		])
	})
})
'use strict'

const { REPORTED } = require('./reported')
const { getTestParser } = require('../get-test-parser')
const { ParseError } = require('../../lib/sax')
const { DOMParser } = require('../../lib/dom-parser')

describe.each(Object.entries(REPORTED))(
	'%s',
	(name, { source, level, match, skippedInHtml }) => {
		describe.each(['text/xml', 'text/html'])('with mimeType %s', (mimeType) => {
			const isHtml = mimeType === 'text/html'
			if (isHtml && skippedInHtml) {
				it(`should not be reported as ${level}`, () => {
					const { errors, parser } = getTestParser()

					parser.parseFromString(source, mimeType)

					// if no report was triggered, the key is not present on `errors`
					expect(errors[level]).toBeUndefined()
				})
			} else {
				it(`should be reported as ${level}`, () => {
					const { errors, parser } = getTestParser()

					parser.parseFromString(source, mimeType)

					const reported = errors[level]
					// store the snapshot, so any change in message can be inspected in the git diff
					expect(reported).toMatchSnapshot()
					// if a match has been defined, filter messages
					expect(
						match ? (reported || []).filter(match) : reported
					).toHaveLength(1)
				})
				if (level === 'fatalError') {
					it(`should throw ParseError in errorHandler.fatalError`, () => {
						const parser = new DOMParser()

						expect(() => parser.parseFromString(source, mimeType)).toThrow(
							ParseError
						)
					})
				} else if (level === 'error') {
					it(`should not catch Error thrown in errorHandler.${level}`, () => {
						let thrown = []
						const errorHandler = {
							[level]: jest.fn((message) => {
								const toThrow = new Error(message)
								thrown.push(toThrow)
								throw toThrow
							}),
						}
						const { parser } = getTestParser({ errorHandler })

						expect(() => parser.parseFromString(source, mimeType)).toThrow(
							Error
						)
						expect(thrown.map(toErrorSnapshot)).toMatchSnapshot()
						match && expect(match(thrown[0].toString())).toBe(true)
					})
				} else if (level === 'warning') {
					it('should escalate Error thrown in errorHandler.warning to errorHandler.error', () => {
						let thrown = []
						const errorHandler = {
							warning: jest.fn((message) => {
								const toThrow = new Error(message)
								thrown.push(toThrow)
								throw toThrow
							}),
							error: jest.fn(),
						}
						const { parser } = getTestParser({ errorHandler })

						parser.parseFromString(source, mimeType)

						expect(errorHandler.warning).toHaveBeenCalledTimes(1)
						expect(errorHandler.error).toHaveBeenCalledTimes(1)
						expect(thrown.map(toErrorSnapshot)).toMatchSnapshot()
						match && expect(match(thrown[0].message)).toBe(true)
					})
				}
			}
		})
	}
)

/**
 * Creates a string from an error that is easily readable in a snapshot
 * - put's the message on one line as first line
 * - picks the first line in the stack trace that is in `lib/sax.js`,
 *   and strips absolute paths and character position from that stack entry
 *   as second line
 * @param {Error} error
 */
function toErrorSnapshot(error) {
	const libSaxMatch = /\/.*\/(lib\/sax\.js)/
	return `${error.message.replace(/([\n\r]+\s*)/g, '||')}\n${error.stack
		.split(/[\n\r]+/)
		// find first line that is from lib/sax.js
		.filter((l) => libSaxMatch.test(l))[0]
		// strip of absolute path
		.replace(libSaxMatch, '$1')
		// strip of position of character in line
		.replace(/:\d+\)$/, ')')}`
}
'use strict'

/**
 * @typedef ErrorReport
 * @property {string} source the XML snippet
 * @property {'error'|'warning'|'fatalError'} level the name of the method triggered
 * @property {?function(msg:string):boolean} match to pick the relevant report when there are multiple
 * @property {?boolean} skippedInHtml Is the error reported when parsing HTML?
 */
/**
 * A collection of XML samples and related information that cause the XMLReader
 * to call methods on `errorHandler`.
 *
 * @type {Record<string, ErrorReport>}}
 */
const REPORTED = {
	/**
	 * Entities need to be in the entityMap to be converted as part of parsing.
	 * xmldom currently doesn't parse entities declared in DTD.
	 *
	 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#wf-entdeclared
	 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#wf-entdeclared
	 */
	WF_EntityDeclared: {
		source: '<xml>&e;</xml>',
		level: 'error',
		match: (msg) => /entity not found/.test(msg),
	},
	/**
	 * Well-formedness constraint: Unique Att Spec
	 *
	 * An attribute name must not appear more than once
	 * in the same start-tag or empty-element tag.
	 *
	 * In the browser:
	 * - as XML it is reported as `error on line 1 at column 17: Attribute a redefined`
	 * - as HTML only the first definition is considered
	 *
	 * In xmldom the behavior is different for namespaces (picks first)
	 * than for other attributes (picks last),
	 * which can be a security issue.
	 *
	 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#uniqattspec
	 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#uniqattspec
	 */
	WF_DuplicateAttribute: {
		source: '<xml a="1" a="2"></xml>',
		level: 'fatalError',
		match: (msg) => /Attribute .* redefined/.test(msg),
	},
	/**
	 * This sample doesn't follow the specified grammar.
	 * In the browser it is reported as `error on line 1 at column 14: expected '>'`,
	 * but still adds the root level element to the dom.
	 */
	SYNTAX_EndTagNotComplete: {
		source: '<xml></xml',
		level: 'error',
		match: (msg) => /end tag name/.test(msg) && /is not complete/.test(msg),
	},
	/**
	 * This sample doesn't follow the specified grammar.
	 * In the browser it is reported as `error on line 1 at column 21: expected '>'`,
	 * but still adds the root level element and inner tag to the dom.
	 */
	SYNTAX_EndTagMaybeNotComplete: {
		source: '<xml><inner></inner </xml>',
		level: 'error',
		match: (msg) => /end tag name/.test(msg) && /maybe not complete/.test(msg),
	},
	/**
	 * This sample doesn't follow the specified grammar.
	 * In the browser it is reported as `error on line 1 at column 6: Comment not terminated`.
	 */
	SYNTAX_UnclosedComment: {
		source: '<!--',
		level: 'error',
		match: (msg) => /Unclosed comment/.test(msg),
	},
	/**
	 * Triggered by lib/sax.js:596, caught in 208
	 * This sample doesn't follow the specified grammar.
	 * In the browser:
	 * - as XML it is reported as
	 * `error on line 1 at column 2: StartTag: invalid element name`
	 * - as HTML it is accepted as characters
	 *
	 */
	SYNTAX_InvalidTagName: {
		source: '<123 />',
		level: 'error',
		match: (msg) => /invalid tagName/.test(msg),
	},
	/**
	 * Triggered by lib/sax.js:602, caught in 208
	 * This sample doesn't follow the specified grammar.
	 * In the browser:
	 * - as XML it is reported as
	 * `error on line 1 at column 6: error parsing attribute name`
	 * - as HTML it is accepted as attribute name
	 */
	SYNTAX_InvalidAttributeName: {
		source: '<xml 123=""/>',
		level: 'error',
		match: (msg) => /invalid attribute/.test(msg),
	},
	/**
	 * This sample doesn't follow the specified grammar.
	 * In the browser it is reported as `error on line 1 at column 5: Couldn't find end of Start Tag xml`.
	 */
	SYNTAX_UnexpectedEndOfInput: {
		source: '<xml',
		level: 'error',
		match: (msg) => /unexpected end of input/.test(msg),
	},
	/**
	 * Triggered by lib/sax.js:392, caught in 208
	 * This sample doesn't follow the specified grammar.
	 * In the browser:
	 * - in XML it is reported as `error on line 1 at column 8: error parsing attribute name`
	 * - in HTML it produces `<xml><a <="" xml=""></a></xml>` (invalid XML?)
	 */
	SYNTAX_ElementClosingNotConnected: {
		source: '<xml><a/ </xml>',
		level: 'error',
		match: (msg) => /must be connected/.test(msg),
	},
	/**
	 * In the Browser (for XML) this is reported as
	 * `error on line 1 at column 6: Extra content at the end of the document`
	 * for HTML it's added to the DOM without anything being reported.
	 */
	WF_UnclosedXmlAttribute: {
		source: '<xml>',
		level: 'warning',
		skippedInHtml: true,
		match: (msg) => /unclosed xml attribute/.test(msg),
	},
	/**
	 * In the browser:
	 * - for XML it is reported as
	 * `error on line 1 at column 10: Specification mandates value for attribute attr`
	 * - for HTML is uses the attribute as one with no value and adds `"value"` to the attribute name
	 *   and is not reporting any issue.
	 */
	WF_AttributeValueMustAfterEqual: {
		source: '<xml attr"value" />',
		level: 'warning',
		match: (msg) => /attribute value must after "="/.test(msg),
	},
	/**
	 * In the browser:
	 * - for XML it is reported as `error on line 1 at column 11: AttValue: " or ' expected`
	 * - for HTML is wraps `value"` with quotes and is not reporting any issue.
	 */
	WF_AttributeMissingStartingQuote: {
		source: '<xml attr=value" />',
		level: 'warning',
		match: (msg) => /missed start quot/.test(msg),
	},
	/**
	 * Triggered by lib/sax.js:264, caught in 208.
	 * TODO: Comment indicates fatalError, change to use errorHandler.fatalError?
	 *
	 * In the browser:
	 * - for XML it is reported as `error on line 1 at column 20: AttValue: ' expected`
	 * - for HTML nothing is added to the DOM.
	 */
	SYNTAX_AttributeMissingEndingQuote: {
		source: '<xml attr="value />',
		level: 'error',
		match: (msg) => /attribute value no end .* match/.test(msg),
	},
	/**
	 * Triggered by lib/sax.js:324
	 * In the browser:
	 * - for XML it is reported as `error on line 1 at column 11: AttValue: " or ' expected`
	 * - for HTML is wraps `value/` with quotes and is not reporting any issue.
	 */
	WF_AttributeMissingQuote: {
		source: '<xml attr=value/>',
		level: 'warning',
		match: (msg) => / missed quot/.test(msg) && /!!/.test(msg) === false,
	},
	/**
	 * Triggered by lib/sax.js:354
	 * This is the only warning reported in this sample.
	 * For some reason the "attribute" that is reported as missing quotes
	 * has the name `&`.
	 * This case is also present in 2 tests in test/html/normalize.test.js
	 *
	 * In the browser:
	 * - for XML it is reported as `error on line 1 at column 8: AttValue: " or ' expected`
	 * - for HTML is yields `<xml a="&amp;" b="&amp;"></xml>` and is not reporting any issue.
	 */
	WF_AttributeMissingQuote2: {
		source: `<xml a=& b="&"/>`,
		level: 'warning',
		match: (msg) => / missed quot/.test(msg) && /!!/.test(msg),
	},
	/**
	 * In the browser:
	 * - for XML it is reported as `error on line 1 at column 9: AttValue: " or ' expected`
	 * - for HTML is yields `<doc a1></xml>` and is not reporting any issue.
	 *
	 * But the XML specifications does not allow that:
	 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Attribute
	 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-Attribute
	 */
	SYNTAX_AttributeEqualMissingValue: {
		source: '<doc a1=></doc>',
		level: 'error',
		match: (msg) => /attribute value missed!!/.test(msg),
	},
	/**
	 * In the browser this is not an issue at all, but just add an attribute without a value.
	 * But the XML specifications does not allow that:
	 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Attribute
	 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-Attribute
	 */
	WF_AttributeMissingValue: {
		source: '<xml attr ></xml>',
		level: 'warning',
		match: (msg) => /missed value/.test(msg) && /instead!!/.test(msg),
	},
	/**
	 * Triggered by lib/sax.js:376
	 * This seems to only be reached when there are two subsequent attributes with a missing value
	 * In the browser this is not an issue at all, but just add an attribute without a value.
	 * But the XML specifications does not allow that:
	 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Attribute
	 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-Attribute
	 */
	WF_AttributeMissingValue2: {
		source: '<xml attr attr2 ></xml>',
		level: 'warning',
		match: (msg) => /missed value/.test(msg) && /instead2!!/.test(msg),
	},
}

module.exports = {
	REPORTED,
}
'use strict'

const { ParseError } = require('../../lib/sax')
const { __DOMHandler, DOMParser } = require('../../lib/dom-parser')

/**
 * All methods implemented on the DOMHandler prototype.
 *
 * @type {string[]}
 */
const DOMHandlerMethods = Object.keys(__DOMHandler.prototype).sort()

/**
 * XMLReader is currently not calling all methods "implemented" by DOMHandler (some are just empty),
 * If this changes the first test will fail.
 *
 * @type {Set<string>}
 */
const UNCALLED_METHODS = new Set([
	'attributeDecl',
	'elementDecl',
	'endEntity',
	'externalEntityDecl',
	'getExternalSubset',
	'ignorableWhitespace',
	'internalEntityDecl',
	'notationDecl',
	'resolveEntity',
	'skippedEntity',
	'startEntity',
	'unparsedEntityDecl',
])

/**
 * Some of the methods DOMParser/XMLReader call during parsing are not guarded by try/catch,
 * hence an error happening in those will stop the parsing process.
 * There is a test to verify this error handling.
 * If it changes this list might need to be changed as well
 *
 * @type {Set<string>}
 */
const UNCAUGHT_METHODS = new Set([
	'characters',
	'endDocument',
	'error',
	'setDocumentLocator',
	'startDocument',
])

function noop() {}

/**
 * A subclass of DOMHandler that mocks all methods for later inspection.
 * As part of the constructor it can be told which method is supposed to throw an error
 * and which error constructor to use.
 *
 * The `methods` property provides the list of all mocks.
 */
class StubDOMHandler extends __DOMHandler {
	constructor(throwingMethod, ErrorClass) {
		super()
		this.methods = []
		DOMHandlerMethods.forEach((method) => {
			const impl = jest.fn(
				method === throwingMethod
					? () => {
							throw new (ErrorClass || ParseError)(
								`StubDOMHandler throwing in ${throwingMethod}`
							)
					  }
					: noop
			)
			impl.mockName(method)
			this[method] = impl
			this.methods.push(impl)
		})
	}
}
/**
 * This sample is triggering all method calls from XMLReader to DOMHandler at least once.
 * This is verified in a test.
 *
 * There is of course no guarantee that it triggers all the places where XMLReader calls DOMHandler.
 * For example not all possible warning and error cases are present in this file,
 * but some, so that the methods are triggered.
 * For testing all the cases of the different error levels,
 * there are samples per case in
 * @see REPORTED
 */
const ALL_METHODS = `<?xml ?>
<!DOCTYPE name >
<![CDATA[ raw ]]>
<root xmlns="namespace">
  <!-- -->
  <element xmlns:x="http://test" x:a="" warning>
    character
  </element>
  <element duplicate="" duplicate="fatal"></mismatch>
</root>
<!--
`

describe('methods called in DOMHandler', () => {
	it('should call "all possible" methods when using StubDOMHandler', () => {
		const domBuilder = new StubDOMHandler()
		const parser = new DOMParser({ domBuilder, locator: {} })
		expect(domBuilder.methods).toHaveLength(DOMHandlerMethods.length)

		parser.parseFromString(ALL_METHODS)

		const uncalledMethodNames = domBuilder.methods
			.filter((m) => m.mock.calls.length === 0)
			.map((m) => m.getMockName())
		expect(uncalledMethodNames).toEqual([...UNCALLED_METHODS.values()].sort())
	})
	describe.each(DOMHandlerMethods.filter((m) => !UNCALLED_METHODS.has(m)))(
		'when DOMHandler.%s throws',
		(throwing) => {
			it('should not catch ParserError', () => {
				const domBuilder = new StubDOMHandler(throwing, ParseError)
				const parser = new DOMParser({ domBuilder, locator: {} })

				expect(() => parser.parseFromString(ALL_METHODS)).toThrow(ParseError)
			})
			const isUncaughtMethod = UNCAUGHT_METHODS.has(throwing)
			it(`${
				isUncaughtMethod ? 'does not' : 'should'
			} catch other Error`, () => {
				const domBuilder = new StubDOMHandler(throwing, Error)
				const parser = new DOMParser({ domBuilder, locator: {} })

				if (isUncaughtMethod) {
					expect(() => parser.parseFromString(ALL_METHODS)).toThrow()
				} else {
					expect(() => parser.parseFromString(ALL_METHODS)).not.toThrow()
				}
			})
		}
	)
})
  "fatalError": Array [
    "[xmldom fatalError]	Attribute a redefined
@#[line:1,col:1]",
  ],
    "[xmldom warning]	attribute \\"123&&456\\" missed quot(\\")!
    "[xmldom warning]	attribute \\"1\\" missed quot(\\")!
    "[xmldom warning]	attribute \\"1/2\\" missed quot(\\")!
    "[xmldom warning]	attribute \\"1\\" missed quot(\\")!
'use strict'

const { getTestParser } = require('../get-test-parser')
describe('doctype', () => {
	describe.each(['SYSTEM', 'PUBLIC'])('%s', (idType) => {
		test.each([
			['outer single', `<!DOCTYPE x ${idType} '\"'><X/>`, "'\"'"],
			['outer double', `<!DOCTYPE x ${idType} "\'"><X/>`, '"\'"'],
		])(
			'should parse single line DOCTYPE with mixed quotes (%s)',
			(_, source, idValue) => {
				const { errors, parser } = getTestParser()

				const actual = parser.parseFromString(source).firstChild

				expect({
					[idType]: idType === 'SYSTEM' ? actual.systemId : actual.publicId,
					name: actual.name,
					...errors,
				}).toEqual({
					[idType]: idValue,
					name: 'x',
				})
			}
		)
	})
})
'use strict'
const { ParseError } = require('../../lib/sax')

describe('ParseError', () => {
	it('should have name ParseError', () => {
		expect(new ParseError('').name).toBe('ParseError')
	})
	it('should be an instance of Error', () => {
		expect(new ParseError('') instanceof Error).toBe(true)
	})

	it('should be an instance of ParseError', () => {
		expect(new ParseError('') instanceof ParseError).toBe(true)
	})

	it('should store first argument as message', () => {
		const error = new ParseError('FROM TEST')
		expect(error.message).toBe('FROM TEST')
	})

	it('should store second argument as locator', () => {
		const locator = {}
		const error = new ParseError('', locator)
		expect(error.locator).toBe(locator)
	})

	it('should have correct StackTrace', () => {
		const error = new ParseError('MESSAGE')
		const stack = error.stack && error.stack.split(/[\n\r]+/)
		expect(stack && stack.length).toBeGreaterThan(1)
		expect(stack[0]).toBe('ParseError: MESSAGE')
		expect(stack[1]).toContain(__filename)
	})

	it('Error should not be instanceof ParseError', () => {
		expect(new Error() instanceof ParseError).toBe(false)
	})
})
    "[xmldom warning]	attribute \\"v1\\" missed quot(\\")!
  "fatalError": Array [
    "[xmldom fatalError]	Attribute x redefined
@#[line:1,col:1]",
  ],