All files / src/web/flowable xmlParser.ts

88.46% Statements 23/26
71.42% Branches 15/21
100% Functions 2/2
88% Lines 22/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53      694x 1579802x 1579655x     147x 139x 139x     139x 139x     8x 6x 6x     6x 6x     2x 2x       692x               694x 2x     692x 692x   692x       690x    
import { DOMParser, type Document as XmlDocument } from '@xmldom/xmldom';
 
function hasDoctypeDeclaration(xml: string): boolean {
	for (let index = 0; index < xml.length; index += 1) {
		if (xml[index] !== '<' || index + 2 >= xml.length || xml[index + 1] !== '!') {
			continue;
		}
 
		if (xml.startsWith('<!--', index)) {
			const commentEnd = xml.indexOf('-->', index + 4);
			Iif (commentEnd === -1) {
				return false;
			}
			index = commentEnd + 2;
			continue;
		}
 
		if (xml.startsWith('<![CDATA[', index)) {
			const cdataEnd = xml.indexOf(']]>', index + 9);
			Iif (cdataEnd === -1) {
				return false;
			}
			index = cdataEnd + 2;
			continue;
		}
 
		Eif (xml.slice(index + 2, index + 9).toUpperCase() === 'DOCTYPE') {
			return true;
		}
	}
 
	return false;
}
 
/**
 * Safely parses an XML string, rejecting DOCTYPE declarations to prevent
 * entity expansion attacks (e.g. "billion laughs").
 */
export function parseXmlDocument(xml: string): XmlDocument {
	if (hasDoctypeDeclaration(xml)) {
		throw new Error('DOCTYPE declarations are not supported in BPMN files.');
	}
 
	const document = new DOMParser().parseFromString(xml, 'application/xml');
	const parserErrors = document.getElementsByTagName('parsererror');
 
	Iif (parserErrors.length > 0) {
		throw new Error(parserErrors[0]?.textContent || 'Invalid XML document.');
	}
 
	return document;
}