fast-xml-validator 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-validator",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Validate XML for Syntax and Business rules without C/C++ based libraries",
5
5
  "main": "./lib/fxv.cjs",
6
6
  "type": "module",
@@ -66,6 +66,6 @@
66
66
  ],
67
67
  "dependencies": {
68
68
  "detailed-xml-validator": "^2.1.0",
69
- "xml-naming": "^0.1.0"
69
+ "xml-naming": "^0.3.0"
70
70
  }
71
- }
71
+ }
package/src/util.js CHANGED
@@ -31,21 +31,3 @@ export function getValue(v) {
31
31
  return '';
32
32
  }
33
33
  }
34
-
35
- /**
36
- * Dangerous property names that could lead to prototype pollution or security issues
37
- */
38
- export const DANGEROUS_PROPERTY_NAMES = [
39
- // '__proto__',
40
- // 'constructor',
41
- // 'prototype',
42
- 'hasOwnProperty',
43
- 'toString',
44
- 'valueOf',
45
- '__defineGetter__',
46
- '__defineSetter__',
47
- '__lookupGetter__',
48
- '__lookupSetter__'
49
- ];
50
-
51
- export const criticalProperties = ["__proto__", "constructor", "prototype"];
package/src/validator.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  import { getAllMatches } from './util.js';
4
- import { name as xmlName, qName } from 'xml-naming';
4
+ import { createValidator } from 'xml-naming';
5
5
  import DocTypeValidator from './DocTypeValidator.js';
6
6
  import ValidationError from './ValidationError.js';
7
7
 
@@ -39,10 +39,16 @@ export function validate(xmlData, options) {
39
39
 
40
40
  const docTypeValidator = new DocTypeValidator(options.docType, xmlVersion);
41
41
 
42
+ // One memoized validator instance per production for this parse call — element/attribute
43
+ // names are repeated across many siblings, so caching pays off. xmlVersion is fixed for
44
+ // the whole parse, so it's safe to bind it once here rather than re-resolving per call.
45
+ const qNameValidator = createValidator('qName', { xmlVersion });
46
+ const nameValidator = createValidator('name', { xmlVersion });
47
+
42
48
  for (let i = 0; i < xmlData.length; i++) {
43
49
  if (xmlData[i] === '<' && xmlData[i + 1] === '?') {
44
50
  i += 2;
45
- i = readPI(xmlData, i, xmlVersion);
51
+ i = readPI(xmlData, i, nameValidator);
46
52
  if (i.err) return i;
47
53
  } else if (xmlData[i] === '<') {
48
54
  //starting of tag
@@ -84,9 +90,9 @@ export function validate(xmlData, options) {
84
90
  i--;
85
91
  }
86
92
 
87
- // use validateQName so namespaced tags (e.g. <ns:tag>) are
93
+ // use the shared qName validator so namespaced tags (e.g. <ns:tag>) are
88
94
  // accepted and malformed ones (e.g. <:tag>, <ns:>, <a:b:c>) are rejected.
89
- if (!validateQName(tagName)) {
95
+ if (!qNameValidator(tagName)) {
90
96
  let msg;
91
97
  if (tagName.trim().length === 0) {
92
98
  msg = "Invalid space after '<'.";
@@ -107,7 +113,7 @@ export function validate(xmlData, options) {
107
113
  //self closing tag
108
114
  const attrStrStart = i - attrStr.length;
109
115
  attrStr = attrStr.substring(0, attrStr.length - 1);
110
- const isValid = validateAttributeString(attrStr, options, xmlVersion);
116
+ const isValid = validateAttributeString(attrStr, options, qNameValidator, xmlVersion);
111
117
  if (isValid === true) {
112
118
  if (reachedRoot === true) {
113
119
  throwError('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, tagStartPos));
@@ -147,7 +153,7 @@ export function validate(xmlData, options) {
147
153
  }
148
154
  }
149
155
  } else {
150
- const isValid = validateAttributeString(attrStr, options, xmlVersion);
156
+ const isValid = validateAttributeString(attrStr, options, qNameValidator, xmlVersion);
151
157
  if (isValid !== true) {
152
158
  throwError(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, i - attrStr.length + isValid.err.line));
153
159
  }
@@ -177,7 +183,7 @@ export function validate(xmlData, options) {
177
183
  continue;
178
184
  } else if (xmlData[i + 1] === '?') {
179
185
  i += 2;
180
- i = readPI(xmlData, i, xmlVersion);
186
+ i = readPI(xmlData, i, nameValidator);
181
187
  if (i.err) return i;
182
188
  } else {
183
189
  break;
@@ -225,7 +231,7 @@ function isWhiteSpace(char) {
225
231
  return char === ' ' || char === '\t' || char === '\n' || char === '\r';
226
232
  }
227
233
 
228
- function readPI(xmlData, i, xmlVersion = '1.0') {
234
+ function readPI(xmlData, i, nameValidator) {
229
235
  const piStart = i; // points just after '<?'
230
236
  const nameStart = i;
231
237
  let nameValidated = false;
@@ -244,7 +250,7 @@ function readPI(xmlData, i, xmlVersion = '1.0') {
244
250
  'XML declaration allowed only at the start of the document.',
245
251
  getLineNumberForPosition(xmlData, piStart - 2) // point at '<'
246
252
  );
247
- } else if (!xmlName(piName, { xmlVersion })) {
253
+ } else if (!nameValidator(piName)) {
248
254
  // PI target must be a valid XML Name (XML 1.0 §2.6)
249
255
  throwError(
250
256
  'InvalidXml',
@@ -366,7 +372,7 @@ function readAttributeStr(xmlData, i) {
366
372
 
367
373
  const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
368
374
 
369
- function validateAttributeString(attrStr, options, xmlVersion) {
375
+ function validateAttributeString(attrStr, options, qNameValidator, xmlVersion) {
370
376
  const matches = getAllMatches(attrStr, validAttrStrRegxp);
371
377
  const attrNames = {};
372
378
 
@@ -383,7 +389,7 @@ function validateAttributeString(attrStr, options, xmlVersion) {
383
389
 
384
390
  //validate attribute names as QNames so namespaced attributes
385
391
  // (e.g. xml:lang, xmlns:xsi) are accepted and malformed ones are rejected.
386
- if (!validateQName(attrName, xmlVersion)) {
392
+ if (!qNameValidator(attrName)) {
387
393
  return { err: { code: 'InvalidAttr', msg: "Attribute '" + attrName + "' is an invalid name.", line: getPositionFromMatch(matches[i]) } };
388
394
  }
389
395
 
@@ -441,18 +447,6 @@ function throwError(code, message, lineNumber) {
441
447
  throw new ValidationError(message, code, lineNumber.line || lineNumber, lineNumber.col);
442
448
  }
443
449
 
444
- /**
445
- * Validate an XML QName (Namespaces in XML 1.0 §2.3).
446
- * Delegates to xml-naming's qName production, which enforces:
447
- * - Non-empty input.
448
- * - At most one colon (used as prefix separator).
449
- * - Neither prefix nor local part may be empty (:foo, ns:, : all rejected).
450
- * - Both parts must satisfy NCName character rules.
451
- */
452
- function validateQName(str, xmlVersion = '1.0') {
453
- return qName(str, { xmlVersion });
454
- }
455
-
456
450
  function getLineNumberForPosition(xmlData, index) {
457
451
  const lines = xmlData.substring(0, index).split(/\r?\n/);
458
452
  return {