fast-xml-parser 4.5.3 → 4.5.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  <small>Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.</small>
2
2
 
3
+ **4.5.4 / 2026-02-26**
4
+ - support strictReservedNames
5
+ - support captureMetaData
6
+ - support maxNestedTags
7
+ - handle non-array input for XML builder when preserveOrder is true (By Angelo Coetzee)
8
+ - Improve security and performance of entity processing
9
+ - new options maxEntitySize, maxExpansionDepth, maxTotalExpansions, maxExpandedLength, allowedTags,tagFilter
10
+ - fast return when no edtity is present
11
+ - improvement replacement logic to reduce number of calls
12
+ - fix: Escape regex char in entity name
13
+ - fix: handle HTML numeric and hex entities when out of range
14
+ - fix #775: transformTagName with allowBooleanAttributes adds an unnecessary attribute
15
+ - Use Uint8Array in place of Buffer in Parser
16
+ - Support EMPTY and ANY with ELEMENT in DOCTYPE
17
+ - fix: support numeric entities with values over 0xFFFF (#726) (By Marc Durdin)
18
+
19
+
3
20
  **4.5.2 / 2025-02-18**
4
21
  - Fix null CDATA to comply with undefined behavior (#701) (By [Matthieu BOHEAS](https://github.com/Kelgors))
5
22
  - Fix(performance): Update check for leaf node in saveTextToParentTag function in OrderedObjParser.js (#707) (By [...](https://github.com/tomingtoming))
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "4.5.3",
3
+ "version": "4.5.5",
4
4
  "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
5
5
  "main": "./src/fxp.js",
6
6
  "scripts": {
7
- "test": "nyc --reporter=lcov --reporter=text jasmine spec/*spec.js",
7
+ "test": "c8 --reporter=lcov --reporter=text jasmine spec/*spec.js",
8
8
  "test-types": "tsc --noEmit spec/typings/typings-test.ts",
9
9
  "unit": "jasmine",
10
- "coverage": "nyc report --reporter html --reporter text -t .nyc_output --report-dir .nyc_output/summary",
11
10
  "perf": "node ./benchmark/perfTest3.js",
12
11
  "lint": "eslint src/*.js spec/*.js",
13
12
  "bundle": "webpack --config webpack-prod.config.js",
@@ -49,10 +48,10 @@
49
48
  "@babel/register": "^7.13.8",
50
49
  "@types/node": "20",
51
50
  "babel-loader": "^8.2.2",
51
+ "c8": "^10.1.3",
52
52
  "eslint": "^8.3.0",
53
53
  "he": "^1.2.0",
54
54
  "jasmine": "^3.6.4",
55
- "nyc": "^15.1.0",
56
55
  "prettier": "^1.19.1",
57
56
  "publish-please": "^5.5.2",
58
57
  "typescript": "5",
@@ -67,6 +66,6 @@
67
66
  }
68
67
  ],
69
68
  "dependencies": {
70
- "strnum": "^1.1.1"
69
+ "strnum": "^1.0.5"
71
70
  }
72
- }
71
+ }
package/src/fxp.d.ts CHANGED
@@ -1,4 +1,67 @@
1
- type X2jOptions = {
1
+ export type ProcessEntitiesOptions = {
2
+ /**
3
+ * Whether to enable entity processing
4
+ *
5
+ * Defaults to `true`
6
+ */
7
+ enabled?: boolean;
8
+
9
+ /**
10
+ * Maximum size in characters for a single entity definition
11
+ *
12
+ * Defaults to `10000`
13
+ */
14
+ maxEntitySize?: number;
15
+
16
+ /**
17
+ * Maximum depth for nested entity references (reserved for future use)
18
+ *
19
+ * Defaults to `10`
20
+ */
21
+ maxExpansionDepth?: number;
22
+
23
+ /**
24
+ * Maximum total number of entity expansions allowed
25
+ *
26
+ * Defaults to `1000`
27
+ */
28
+ maxTotalExpansions?: number;
29
+
30
+ /**
31
+ * Maximum total expanded content length in characters
32
+ *
33
+ * Defaults to `100000`
34
+ */
35
+ maxExpandedLength?: number;
36
+
37
+ /**
38
+ * Maximum number of entities allowed in the XML
39
+ *
40
+ * Defaults to `100`
41
+ */
42
+ maxEntityCount?: number;
43
+
44
+ /**
45
+ * Array of tag names where entity replacement is allowed.
46
+ * If null, entities are replaced in all tags.
47
+ *
48
+ * Defaults to `null`
49
+ */
50
+ allowedTags?: string[] | null;
51
+
52
+ /**
53
+ * Custom filter function to determine if entities should be replaced in a tag
54
+ *
55
+ * @param tagName - The name of the current tag
56
+ * @param jPath - The jPath of the current tag
57
+ * @returns `true` to allow entity replacement, `false` to skip
58
+ *
59
+ * Defaults to `null`
60
+ */
61
+ tagFilter?: ((tagName: string, jPath: string) => boolean) | null;
62
+ };
63
+
64
+ export type X2jOptions = {
2
65
  /**
3
66
  * Preserve the order of tags in resulting JS object
4
67
  *
@@ -10,7 +73,7 @@ type X2jOptions = {
10
73
  * Give a prefix to the attribute name in the resulting JS object
11
74
  *
12
75
  * Defaults to '@_'
13
- */
76
+ */
14
77
  attributeNamePrefix?: string;
15
78
 
16
79
  /**
@@ -64,7 +127,7 @@ type X2jOptions = {
64
127
  parseTagValue?: boolean;
65
128
 
66
129
  /**
67
- * Whether to parse tag value with `strnum` package
130
+ * Whether to parse attribute value with `strnum` package
68
131
  *
69
132
  * Defaults to `false`
70
133
  */
@@ -161,9 +224,15 @@ type X2jOptions = {
161
224
  /**
162
225
  * Whether to process default and DOCTYPE entities
163
226
  *
227
+ * When `true` - enables entity processing with default limits
228
+ *
229
+ * When `false` - disables all entity processing
230
+ *
231
+ * When `ProcessEntitiesOptions` - enables entity processing with custom configuration
232
+ *
164
233
  * Defaults to `true`
165
234
  */
166
- processEntities?: boolean;
235
+ processEntities?: boolean | ProcessEntitiesOptions;
167
236
 
168
237
  /**
169
238
  * Whether to process HTML entities
@@ -209,24 +278,56 @@ type X2jOptions = {
209
278
  *
210
279
  * Defaults to `(tagName, jPath, attrs) => tagName`
211
280
  */
212
- updateTag?: (tagName: string, jPath: string, attrs: {[k: string]: string}) => string | boolean;
281
+ updateTag?: (tagName: string, jPath: string, attrs: { [k: string]: string }) => string | boolean;
282
+
283
+ /**
284
+ * If true, adds a Symbol to all object nodes, accessible by {@link XMLParser.getMetaDataSymbol} with
285
+ * metadata about each the node in the XML file.
286
+ */
287
+ captureMetaData?: boolean;
288
+
289
+ /**
290
+ * Maximum number of nested tags
291
+ *
292
+ * Defaults to `100`
293
+ */
294
+ maxNestedTags?: number;
295
+
296
+ /**
297
+ * Whether to strictly validate tag names
298
+ *
299
+ * Defaults to `true`
300
+ */
301
+ strictReservedNames?: boolean;
302
+
303
+ /**
304
+ * Function to sanitize dangerous property names
305
+ *
306
+ * @param name - The name of the property
307
+ * @returns {string} The sanitized name
308
+ *
309
+ * Defaults to `(name) => __name`
310
+ */
311
+ onDangerousProperty?: (name: string) => string;
213
312
  };
214
313
 
215
- type strnumOptions = {
314
+
315
+
316
+ export type strnumOptions = {
216
317
  hex: boolean;
217
318
  leadingZeros: boolean,
218
319
  skipLike?: RegExp,
219
320
  eNotation?: boolean
220
321
  }
221
322
 
222
- type validationOptions = {
323
+ export type validationOptions = {
223
324
  /**
224
325
  * Whether to allow attributes without value
225
326
  *
226
327
  * Defaults to `false`
227
328
  */
228
329
  allowBooleanAttributes?: boolean;
229
-
330
+
230
331
  /**
231
332
  * List of tags without closing tags
232
333
  *
@@ -235,12 +336,12 @@ type validationOptions = {
235
336
  unpairedTags?: string[];
236
337
  };
237
338
 
238
- type XmlBuilderOptions = {
339
+ export type XmlBuilderOptions = {
239
340
  /**
240
341
  * Give a prefix to the attribute name in the resulting JS object
241
342
  *
242
343
  * Defaults to '@_'
243
- */
344
+ */
244
345
  attributeNamePrefix?: string;
245
346
 
246
347
  /**
@@ -385,22 +486,29 @@ type XmlBuilderOptions = {
385
486
 
386
487
 
387
488
  oneListGroup?: boolean;
489
+
490
+ /**
491
+ * Maximum number of nested tags
492
+ *
493
+ * Defaults to `100`
494
+ */
495
+ maxNestedTags?: number;
388
496
  };
389
497
 
390
- type ESchema = string | object | Array<string|object>;
498
+ type ESchema = string | object | Array<string | object>;
391
499
 
392
- type ValidationError = {
393
- err: {
500
+ export type ValidationError = {
501
+ err: {
394
502
  code: string;
395
503
  msg: string,
396
504
  line: number,
397
- col: number
505
+ col: number
398
506
  };
399
507
  };
400
508
 
401
509
  export class XMLParser {
402
510
  constructor(options?: X2jOptions);
403
- parse(xmlData: string | Buffer ,validationOptions?: validationOptions | boolean): any;
511
+ parse(xmlData: string | Uint8Array, validationOptions?: validationOptions | boolean): any;
404
512
  /**
405
513
  * Add Entity which is not by default supported by this library
406
514
  * @param entityIdentifier {string} Eg: 'ent' for &ent;
@@ -409,10 +517,10 @@ export class XMLParser {
409
517
  addEntity(entityIdentifier: string, entityValue: string): void;
410
518
  }
411
519
 
412
- export class XMLValidator{
413
- static validate( xmlData: string, options?: validationOptions): true | ValidationError;
520
+ export class XMLValidator {
521
+ static validate(xmlData: string, options?: validationOptions): true | ValidationError;
414
522
  }
415
523
  export class XMLBuilder {
416
524
  constructor(options?: XmlBuilderOptions);
417
- build(jObj: any): any;
525
+ build(jObj: any): string;
418
526
  }
package/src/util.js CHANGED
@@ -5,7 +5,7 @@ const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
5
5
  const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*'
6
6
  const regexName = new RegExp('^' + nameRegexp + '$');
7
7
 
8
- const getAllMatches = function(string, regex) {
8
+ const getAllMatches = function (string, regex) {
9
9
  const matches = [];
10
10
  let match = regex.exec(string);
11
11
  while (match) {
@@ -21,16 +21,16 @@ const getAllMatches = function(string, regex) {
21
21
  return matches;
22
22
  };
23
23
 
24
- const isName = function(string) {
24
+ const isName = function (string) {
25
25
  const match = regexName.exec(string);
26
26
  return !(match === null || typeof match === 'undefined');
27
27
  };
28
28
 
29
- exports.isExist = function(v) {
29
+ exports.isExist = function (v) {
30
30
  return typeof v !== 'undefined';
31
31
  };
32
32
 
33
- exports.isEmptyObject = function(obj) {
33
+ exports.isEmptyObject = function (obj) {
34
34
  return Object.keys(obj).length === 0;
35
35
  };
36
36
 
@@ -39,13 +39,13 @@ exports.isEmptyObject = function(obj) {
39
39
  * @param {*} target
40
40
  * @param {*} a
41
41
  */
42
- exports.merge = function(target, a, arrayMode) {
42
+ exports.merge = function (target, a, arrayMode) {
43
43
  if (a) {
44
44
  const keys = Object.keys(a); // will return an array of own properties
45
45
  const len = keys.length; //don't make it inline
46
46
  for (let i = 0; i < len; i++) {
47
47
  if (arrayMode === 'strict') {
48
- target[keys[i]] = [ a[keys[i]] ];
48
+ target[keys[i]] = [a[keys[i]]];
49
49
  } else {
50
50
  target[keys[i]] = a[keys[i]];
51
51
  }
@@ -56,7 +56,7 @@ exports.merge = function(target, a, arrayMode) {
56
56
  return Object.assign(b,a);
57
57
  } */
58
58
 
59
- exports.getValue = function(v) {
59
+ exports.getValue = function (v) {
60
60
  if (exports.isExist(v)) {
61
61
  return v;
62
62
  } else {
@@ -64,9 +64,26 @@ exports.getValue = function(v) {
64
64
  }
65
65
  };
66
66
 
67
- // const fakeCall = function(a) {return a;};
68
- // const fakeCallNoReturn = function() {};
67
+ /**
68
+ * Dangerous property names that could lead to prototype pollution or security issues
69
+ */
70
+ const DANGEROUS_PROPERTY_NAMES = [
71
+ // '__proto__',
72
+ // 'constructor',
73
+ // 'prototype',
74
+ 'hasOwnProperty',
75
+ 'toString',
76
+ 'valueOf',
77
+ '__defineGetter__',
78
+ '__defineSetter__',
79
+ '__lookupGetter__',
80
+ '__lookupSetter__'
81
+ ];
82
+
83
+ const criticalProperties = ["__proto__", "constructor", "prototype"];
69
84
 
70
85
  exports.isName = isName;
71
86
  exports.getAllMatches = getAllMatches;
72
87
  exports.nameRegexp = nameRegexp;
88
+ exports.DANGEROUS_PROPERTY_NAMES = DANGEROUS_PROPERTY_NAMES;
89
+ exports.criticalProperties = criticalProperties;
@@ -18,10 +18,21 @@ function arrToStr(arr, options, jPath, indentation) {
18
18
  let xmlStr = "";
19
19
  let isPreviousElementTag = false;
20
20
 
21
+
22
+ if (!Array.isArray(arr)) {
23
+ // Non-array values (e.g. string tag values) should be treated as text content
24
+ if (arr !== undefined && arr !== null) {
25
+ let text = arr.toString();
26
+ text = replaceEntitiesValue(text, options);
27
+ return text;
28
+ }
29
+ return "";
30
+ }
31
+
21
32
  for (let i = 0; i < arr.length; i++) {
22
33
  const tagObj = arr[i];
23
34
  const tagName = propName(tagObj);
24
- if(tagName === undefined) continue;
35
+ if (tagName === undefined) continue;
25
36
 
26
37
  let newJPath = "";
27
38
  if (jPath.length === 0) newJPath = tagName
@@ -92,7 +103,7 @@ function propName(obj) {
92
103
  const keys = Object.keys(obj);
93
104
  for (let i = 0; i < keys.length; i++) {
94
105
  const key = keys[i];
95
- if(!obj.hasOwnProperty(key)) continue;
106
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
96
107
  if (key !== ":@") return key;
97
108
  }
98
109
  }
@@ -101,7 +112,7 @@ function attr_to_str(attrMap, options) {
101
112
  let attrStr = "";
102
113
  if (attrMap && !options.ignoreAttributes) {
103
114
  for (let attr in attrMap) {
104
- if(!attrMap.hasOwnProperty(attr)) continue;
115
+ if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
105
116
  let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
106
117
  attrVal = replaceEntitiesValue(attrVal, options);
107
118
  if (attrVal === true && options.suppressBooleanAttributes) {