UNPKG

fast-xml-parser

Version:

Validate XML, Parse XML, Build XML without C/C++ based libraries

159 lines (143 loc) 5.16 kB
import { DANGEROUS_PROPERTY_NAMES, criticalProperties } from "../util.js"; const defaultOnDangerousProperty = (name) => { if (DANGEROUS_PROPERTY_NAMES.includes(name)) { return "__" + name; } return name; }; export const defaultOptions = { preserveOrder: false, attributeNamePrefix: '@_', attributesGroupName: false, textNodeName: '#text', ignoreAttributes: true, removeNSPrefix: false, // remove NS from tag name or attribute name if true allowBooleanAttributes: false, //a tag can have attributes without any value //ignoreRootElement : false, parseTagValue: true, parseAttributeValue: false, trimValues: true, //Trim string values of tag and attributes cdataPropName: false, numberParseOptions: { hex: true, leadingZeros: true, eNotation: true }, tagValueProcessor: function (tagName, val) { return val; }, attributeValueProcessor: function (attrName, val) { return val; }, stopNodes: [], //nested tags will not be parsed even for errors alwaysCreateTextNode: false, isArray: () => false, commentPropName: false, unpairedTags: [], processEntities: true, htmlEntities: false, ignoreDeclaration: false, ignorePiTags: false, transformTagName: false, transformAttributeName: false, updateTag: function (tagName, jPath, attrs) { return tagName }, // skipEmptyListItem: false captureMetaData: false, maxNestedTags: 100, strictReservedNames: true, jPath: true, // if true, pass jPath string to callbacks; if false, pass matcher instance onDangerousProperty: defaultOnDangerousProperty }; /** * Validates that a property name is safe to use * @param {string} propertyName - The property name to validate * @param {string} optionName - The option field name (for error message) * @throws {Error} If property name is dangerous */ function validatePropertyName(propertyName, optionName) { if (typeof propertyName !== 'string') { return; // Only validate string property names } const normalized = propertyName.toLowerCase(); if (DANGEROUS_PROPERTY_NAMES.some(dangerous => normalized === dangerous.toLowerCase())) { throw new Error( `[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution` ); } if (criticalProperties.some(dangerous => normalized === dangerous.toLowerCase())) { throw new Error( `[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution` ); } } /** * Normalizes processEntities option for backward compatibility * @param {boolean|object} value * @returns {object} Always returns normalized object */ function normalizeProcessEntities(value) { // Boolean backward compatibility if (typeof value === 'boolean') { return { enabled: value, // true or false maxEntitySize: 10000, maxExpansionDepth: 10, maxTotalExpansions: 1000, maxExpandedLength: 100000, maxEntityCount: 100, allowedTags: null, tagFilter: null }; } // Object config - merge with defaults if (typeof value === 'object' && value !== null) { return { enabled: value.enabled !== false, maxEntitySize: Math.max(1, value.maxEntitySize ?? 10000), maxExpansionDepth: Math.max(1, value.maxExpansionDepth ?? 10000), maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? Infinity), maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000), maxEntityCount: Math.max(1, value.maxEntityCount ?? 1000), allowedTags: value.allowedTags ?? null, tagFilter: value.tagFilter ?? null }; } // Default to enabled with limits return normalizeProcessEntities(true); } export const buildOptions = function (options) { const built = Object.assign({}, defaultOptions, options); // Validate property names to prevent prototype pollution const propertyNameOptions = [ { value: built.attributeNamePrefix, name: 'attributeNamePrefix' }, { value: built.attributesGroupName, name: 'attributesGroupName' }, { value: built.textNodeName, name: 'textNodeName' }, { value: built.cdataPropName, name: 'cdataPropName' }, { value: built.commentPropName, name: 'commentPropName' } ]; for (const { value, name } of propertyNameOptions) { if (value) { validatePropertyName(value, name); } } if (built.onDangerousProperty === null) { built.onDangerousProperty = defaultOnDangerousProperty; } // Always normalize processEntities for backward compatibility and validation built.processEntities = normalizeProcessEntities(built.processEntities); // Convert old-style stopNodes for backward compatibility if (built.stopNodes && Array.isArray(built.stopNodes)) { built.stopNodes = built.stopNodes.map(node => { if (typeof node === 'string' && node.startsWith('*.')) { // Old syntax: *.tagname meant "tagname anywhere" // Convert to new syntax: ..tagname return '..' + node.substring(2); } return node; }); } //console.debug(built.processEntities) return built; };