@cdklabs/cdk-ecs-codedeploy 0.0.433 → 0.0.434

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.
Files changed (83) hide show
  1. package/.jsii +3 -3
  2. package/.jsii.tabl.json +1 -1
  3. package/lib/api-canary/index.js +1 -1
  4. package/lib/ecs-appspec/index.js +1 -1
  5. package/lib/ecs-deployment/index.js +1 -1
  6. package/lib/ecs-patterns/application-load-balanced-codedeployed-fargate-service.js +1 -1
  7. package/node_modules/@aws-sdk/client-codedeploy/package.json +15 -15
  8. package/node_modules/@aws-sdk/core/package.json +4 -4
  9. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  10. package/node_modules/@aws-sdk/credential-provider-http/package.json +5 -5
  11. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  12. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  13. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  14. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  15. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  16. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  17. package/node_modules/@aws-sdk/middleware-user-agent/package.json +3 -3
  18. package/node_modules/@aws-sdk/nested-clients/package.json +14 -14
  19. package/node_modules/@aws-sdk/region-config-resolver/package.json +2 -2
  20. package/node_modules/@aws-sdk/token-providers/package.json +3 -3
  21. package/node_modules/@aws-sdk/util-user-agent-node/package.json +2 -2
  22. package/node_modules/@aws-sdk/xml-builder/dist-cjs/index.js +21 -11
  23. package/node_modules/@aws-sdk/xml-builder/dist-cjs/xml-parser.js +5 -1
  24. package/node_modules/@aws-sdk/xml-builder/dist-es/escape-attribute.js +8 -1
  25. package/node_modules/@aws-sdk/xml-builder/dist-es/escape-element.js +13 -10
  26. package/node_modules/@aws-sdk/xml-builder/dist-es/xml-parser.js +5 -1
  27. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/CHANGELOG.md +40 -4
  28. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/README.md +8 -7
  29. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxbuilder.min.js +1 -1
  30. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxbuilder.min.js.map +1 -1
  31. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.cjs +1 -1
  32. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.d.cts +82 -20
  33. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.min.js +1 -1
  34. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.min.js.map +1 -1
  35. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxparser.min.js +1 -1
  36. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxparser.min.js.map +1 -1
  37. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxvalidator.min.js.map +1 -1
  38. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/pem.d.cts +148 -0
  39. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/package.json +4 -3
  40. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/fxp.d.ts +75 -19
  41. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/pem.d.ts +135 -0
  42. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/util.js +18 -0
  43. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js +37 -18
  44. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js +76 -5
  45. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +298 -116
  46. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js +2 -2
  47. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/src/xmlparser/node2json.js +65 -15
  48. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/CHANGELOG.md +9 -2
  49. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/package.json +1 -1
  50. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/strnum.js +12 -6
  51. package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/tests/strnum_test.js +3 -0
  52. package/node_modules/@aws-sdk/xml-builder/node_modules/strnum/tests/temp.js +8 -0
  53. package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
  54. package/node_modules/@smithy/config-resolver/dist-cjs/index.js +12 -0
  55. package/node_modules/@smithy/config-resolver/dist-es/endpointsConfig/NodeUseDualstackEndpointConfigOptions.js +5 -0
  56. package/node_modules/@smithy/config-resolver/dist-es/endpointsConfig/NodeUseFipsEndpointConfigOptions.js +5 -0
  57. package/node_modules/@smithy/config-resolver/dist-types/endpointsConfig/NodeUseDualstackEndpointConfigOptions.d.ts +6 -0
  58. package/node_modules/@smithy/config-resolver/dist-types/endpointsConfig/NodeUseFipsEndpointConfigOptions.d.ts +6 -0
  59. package/node_modules/@smithy/config-resolver/dist-types/ts3.4/endpointsConfig/NodeUseDualstackEndpointConfigOptions.d.ts +6 -0
  60. package/node_modules/@smithy/config-resolver/dist-types/ts3.4/endpointsConfig/NodeUseFipsEndpointConfigOptions.d.ts +6 -0
  61. package/node_modules/@smithy/config-resolver/package.json +1 -1
  62. package/node_modules/@smithy/middleware-endpoint/dist-types/resolveEndpointConfig.d.ts +2 -2
  63. package/node_modules/@smithy/middleware-endpoint/dist-types/ts3.4/resolveEndpointConfig.d.ts +2 -2
  64. package/node_modules/@smithy/middleware-endpoint/package.json +1 -1
  65. package/node_modules/@smithy/middleware-retry/package.json +2 -2
  66. package/node_modules/@smithy/smithy-client/package.json +2 -2
  67. package/node_modules/@smithy/util-defaults-mode-browser/package.json +2 -2
  68. package/node_modules/@smithy/util-defaults-mode-node/package.json +3 -3
  69. package/node_modules/path-expression-matcher/README.md +98 -13
  70. package/node_modules/path-expression-matcher/lib/pem.cjs +1 -1
  71. package/node_modules/path-expression-matcher/lib/pem.d.cts +188 -0
  72. package/node_modules/path-expression-matcher/lib/pem.min.js +1 -1
  73. package/node_modules/path-expression-matcher/lib/pem.min.js.map +1 -1
  74. package/node_modules/path-expression-matcher/package.json +2 -2
  75. package/node_modules/path-expression-matcher/src/Matcher.js +84 -0
  76. package/node_modules/path-expression-matcher/src/index.d.ts +152 -0
  77. package/package.json +4 -4
  78. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/.github/SECURITY.md +0 -0
  79. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/.vscode/launch.json +0 -0
  80. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/LICENSE +0 -0
  81. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/README.md +0 -0
  82. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/algo.stflow +0 -0
  83. /package/node_modules/{strnum → @aws-sdk/xml-builder/node_modules/strnum}/tests/infinity_test.js +0 -0
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
  ///@ts-check
3
3
 
4
- import { getAllMatches, isExist } from '../util.js';
4
+ import { getAllMatches, isExist, DANGEROUS_PROPERTY_NAMES, criticalProperties } from '../util.js';
5
5
  import xmlNode from './xmlNode.js';
6
6
  import DocTypeReader from './DocTypeReader.js';
7
7
  import toNumber from "strnum";
8
8
  import getIgnoreAttributesFn from "../ignoreAttributes.js";
9
+ import { Expression, Matcher } from 'path-expression-matcher';
9
10
 
10
11
  // const regx =
11
12
  // '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
@@ -14,6 +15,57 @@ import getIgnoreAttributesFn from "../ignoreAttributes.js";
14
15
  //const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
15
16
  //const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
16
17
 
18
+ // Helper functions for attribute and namespace handling
19
+
20
+ /**
21
+ * Extract raw attributes (without prefix) from prefixed attribute map
22
+ * @param {object} prefixedAttrs - Attributes with prefix from buildAttributesMap
23
+ * @param {object} options - Parser options containing attributeNamePrefix
24
+ * @returns {object} Raw attributes for matcher
25
+ */
26
+ function extractRawAttributes(prefixedAttrs, options) {
27
+ if (!prefixedAttrs) return {};
28
+
29
+ // Handle attributesGroupName option
30
+ const attrs = options.attributesGroupName
31
+ ? prefixedAttrs[options.attributesGroupName]
32
+ : prefixedAttrs;
33
+
34
+ if (!attrs) return {};
35
+
36
+ const rawAttrs = {};
37
+ for (const key in attrs) {
38
+ // Remove the attribute prefix to get raw name
39
+ if (key.startsWith(options.attributeNamePrefix)) {
40
+ const rawName = key.substring(options.attributeNamePrefix.length);
41
+ rawAttrs[rawName] = attrs[key];
42
+ } else {
43
+ // Attribute without prefix (shouldn't normally happen, but be safe)
44
+ rawAttrs[key] = attrs[key];
45
+ }
46
+ }
47
+ return rawAttrs;
48
+ }
49
+
50
+ /**
51
+ * Extract namespace from raw tag name
52
+ * @param {string} rawTagName - Tag name possibly with namespace (e.g., "soap:Envelope")
53
+ * @returns {string|undefined} Namespace or undefined
54
+ */
55
+ function extractNamespace(rawTagName) {
56
+ if (!rawTagName || typeof rawTagName !== 'string') return undefined;
57
+
58
+ const colonIndex = rawTagName.indexOf(':');
59
+ if (colonIndex !== -1 && colonIndex > 0) {
60
+ const ns = rawTagName.substring(0, colonIndex);
61
+ // Don't treat xmlns as a namespace
62
+ if (ns !== 'xmlns') {
63
+ return ns;
64
+ }
65
+ }
66
+ return undefined;
67
+ }
68
+
17
69
  export default class OrderedObjParser {
18
70
  constructor(options) {
19
71
  this.options = options;
@@ -58,16 +110,27 @@ export default class OrderedObjParser {
58
110
  this.entityExpansionCount = 0;
59
111
  this.currentExpandedLength = 0;
60
112
 
113
+ // Initialize path matcher for path-expression-matcher
114
+ this.matcher = new Matcher();
115
+
116
+ // Live read-only proxy of matcher — PEM creates and caches this internally.
117
+ // All user callbacks receive this instead of the mutable matcher.
118
+ this.readonlyMatcher = this.matcher.readOnly();
119
+
120
+ // Flag to track if current node is a stop node (optimization)
121
+ this.isCurrentNodeStopNode = false;
122
+
123
+ // Pre-compile stopNodes expressions
61
124
  if (this.options.stopNodes && this.options.stopNodes.length > 0) {
62
- this.stopNodesExact = new Set();
63
- this.stopNodesWildcard = new Set();
125
+ this.stopNodeExpressions = [];
64
126
  for (let i = 0; i < this.options.stopNodes.length; i++) {
65
127
  const stopNodeExp = this.options.stopNodes[i];
66
- if (typeof stopNodeExp !== 'string') continue;
67
- if (stopNodeExp.startsWith("*.")) {
68
- this.stopNodesWildcard.add(stopNodeExp.substring(2));
69
- } else {
70
- this.stopNodesExact.add(stopNodeExp);
128
+ if (typeof stopNodeExp === 'string') {
129
+ // Convert string to Expression object
130
+ this.stopNodeExpressions.push(new Expression(stopNodeExp));
131
+ } else if (stopNodeExp instanceof Expression) {
132
+ // Already an Expression object
133
+ this.stopNodeExpressions.push(stopNodeExp);
71
134
  }
72
135
  }
73
136
  }
@@ -90,7 +153,7 @@ function addExternalEntities(externalEntities) {
90
153
  /**
91
154
  * @param {string} val
92
155
  * @param {string} tagName
93
- * @param {string} jPath
156
+ * @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
94
157
  * @param {boolean} dontTrim
95
158
  * @param {boolean} hasAttributes
96
159
  * @param {boolean} isLeafNode
@@ -104,7 +167,9 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode,
104
167
  if (val.length > 0) {
105
168
  if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
106
169
 
107
- const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
170
+ // Pass jPath string or matcher based on options.jPath setting
171
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
172
+ const newval = this.options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode);
108
173
  if (newval === null || newval === undefined) {
109
174
  //don't parse
110
175
  return val;
@@ -151,25 +216,58 @@ function buildAttributesMap(attrStr, jPath, tagName) {
151
216
  const matches = getAllMatches(attrStr, attrsRegx);
152
217
  const len = matches.length; //don't make it inline
153
218
  const attrs = {};
219
+
220
+ // First pass: parse all attributes and update matcher with raw values
221
+ // This ensures the matcher has all attribute values when processors run
222
+ const rawAttrsForMatcher = {};
223
+ for (let i = 0; i < len; i++) {
224
+ const attrName = this.resolveNameSpace(matches[i][1]);
225
+ const oldVal = matches[i][4];
226
+
227
+ if (attrName.length && oldVal !== undefined) {
228
+ let parsedVal = oldVal;
229
+ if (this.options.trimValues) {
230
+ parsedVal = parsedVal.trim();
231
+ }
232
+ parsedVal = this.replaceEntitiesValue(parsedVal, tagName, this.readonlyMatcher);
233
+ rawAttrsForMatcher[attrName] = parsedVal;
234
+ }
235
+ }
236
+
237
+ // Update matcher with raw attribute values BEFORE running processors
238
+ if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) {
239
+ jPath.updateCurrent(rawAttrsForMatcher);
240
+ }
241
+
242
+ // Second pass: now process attributes with matcher having full attribute context
154
243
  for (let i = 0; i < len; i++) {
155
244
  const attrName = this.resolveNameSpace(matches[i][1]);
156
- if (this.ignoreAttributesFn(attrName, jPath)) {
245
+
246
+ // Convert jPath to string if needed for ignoreAttributesFn
247
+ const jPathStr = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
248
+ if (this.ignoreAttributesFn(attrName, jPathStr)) {
157
249
  continue
158
250
  }
251
+
159
252
  let oldVal = matches[i][4];
160
253
  let aName = this.options.attributeNamePrefix + attrName;
254
+
161
255
  if (attrName.length) {
162
256
  if (this.options.transformAttributeName) {
163
257
  aName = this.options.transformAttributeName(aName);
164
258
  }
165
- if (aName === "__proto__") aName = "#__proto__";
259
+ //if (aName === "__proto__") aName = "#__proto__";
260
+ aName = sanitizeName(aName, this.options);
166
261
 
167
262
  if (oldVal !== undefined) {
168
263
  if (this.options.trimValues) {
169
264
  oldVal = oldVal.trim();
170
265
  }
171
- oldVal = this.replaceEntitiesValue(oldVal, tagName, jPath);
172
- const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
266
+ oldVal = this.replaceEntitiesValue(oldVal, tagName, this.readonlyMatcher);
267
+
268
+ // Pass jPath string or readonlyMatcher based on options.jPath setting
269
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
270
+ const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher);
173
271
  if (newVal === null || newVal === undefined) {
174
272
  //don't parse
175
273
  attrs[aName] = oldVal;
@@ -189,6 +287,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
189
287
  }
190
288
  }
191
289
  }
290
+
192
291
  if (!Object.keys(attrs).length) {
193
292
  return;
194
293
  }
@@ -206,7 +305,9 @@ const parseXml = function (xmlData) {
206
305
  const xmlObj = new xmlNode('!xml');
207
306
  let currentNode = xmlObj;
208
307
  let textData = "";
209
- let jPath = "";
308
+
309
+ // Reset matcher for new document
310
+ this.matcher.reset();
210
311
 
211
312
  // Reset entity expansion counters for this document
212
313
  this.entityExpansionCount = 0;
@@ -229,27 +330,25 @@ const parseXml = function (xmlData) {
229
330
  }
230
331
  }
231
332
 
232
- if (this.options.transformTagName) {
233
- tagName = this.options.transformTagName(tagName);
234
- }
333
+ tagName = transformTagName(this.options.transformTagName, tagName, "", this.options).tagName;
235
334
 
236
335
  if (currentNode) {
237
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
336
+ textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
238
337
  }
239
338
 
240
339
  //check if last tag of nested tag was unpaired tag
241
- const lastTagName = jPath.substring(jPath.lastIndexOf(".") + 1);
340
+ const lastTagName = this.matcher.getCurrentTag();
242
341
  if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) {
243
342
  throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
244
343
  }
245
- let propIndex = 0
246
344
  if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
247
- propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.') - 1)
345
+ // Pop the unpaired tag
346
+ this.matcher.pop();
248
347
  this.tagsNodeStack.pop();
249
- } else {
250
- propIndex = jPath.lastIndexOf(".");
251
348
  }
252
- jPath = jPath.substring(0, propIndex);
349
+ // Pop the closing tag
350
+ this.matcher.pop();
351
+ this.isCurrentNodeStopNode = false; // Reset flag when closing tag
253
352
 
254
353
  currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
255
354
  textData = "";
@@ -259,7 +358,7 @@ const parseXml = function (xmlData) {
259
358
  let tagData = readTagExp(xmlData, i, false, "?>");
260
359
  if (!tagData) throw new Error("Pi Tag is not closed.");
261
360
 
262
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
361
+ textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
263
362
  if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) {
264
363
  //do nothing
265
364
  } else {
@@ -268,9 +367,9 @@ const parseXml = function (xmlData) {
268
367
  childNode.add(this.options.textNodeName, "");
269
368
 
270
369
  if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
271
- childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
370
+ childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName);
272
371
  }
273
- this.addChild(currentNode, childNode, jPath, i);
372
+ this.addChild(currentNode, childNode, this.readonlyMatcher, i);
274
373
  }
275
374
 
276
375
 
@@ -280,7 +379,7 @@ const parseXml = function (xmlData) {
280
379
  if (this.options.commentPropName) {
281
380
  const comment = xmlData.substring(i + 4, endIndex - 2);
282
381
 
283
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
382
+ textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
284
383
 
285
384
  currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
286
385
  }
@@ -293,9 +392,9 @@ const parseXml = function (xmlData) {
293
392
  const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
294
393
  const tagExp = xmlData.substring(i + 9, closeIndex);
295
394
 
296
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
395
+ textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
297
396
 
298
- let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
397
+ let val = this.parseTextData(tagExp, currentNode.tagname, this.readonlyMatcher, true, false, true, true);
299
398
  if (val == undefined) val = "";
300
399
 
301
400
  //cdata should be set even if it is 0 length string
@@ -308,24 +407,27 @@ const parseXml = function (xmlData) {
308
407
  i = closeIndex + 2;
309
408
  } else {//Opening tag
310
409
  let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
410
+
411
+ // Safety check: readTagExp can return undefined
412
+ if (!result) {
413
+ // Log context for debugging
414
+ const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlData.length, i + 50));
415
+ throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`);
416
+ }
417
+
311
418
  let tagName = result.tagName;
312
419
  const rawTagName = result.rawTagName;
313
420
  let tagExp = result.tagExp;
314
421
  let attrExpPresent = result.attrExpPresent;
315
422
  let closeIndex = result.closeIndex;
316
423
 
317
- if (this.options.transformTagName) {
318
- //console.log(tagExp, tagName)
319
- const newTagName = this.options.transformTagName(tagName);
320
- if (tagExp === tagName) {
321
- tagExp = newTagName
322
- }
323
- tagName = newTagName;
324
- }
424
+ ({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options));
325
425
 
326
426
  if (this.options.strictReservedNames &&
327
427
  (tagName === this.options.commentPropName
328
428
  || tagName === this.options.cdataPropName
429
+ || tagName === this.options.textNodeName
430
+ || tagName === this.options.attributesGroupName
329
431
  )) {
330
432
  throw new Error(`Invalid tag name: ${tagName}`);
331
433
  }
@@ -334,7 +436,7 @@ const parseXml = function (xmlData) {
334
436
  if (currentNode && textData) {
335
437
  if (currentNode.tagname !== '!xml') {
336
438
  //when nested tag is found
337
- textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
439
+ textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher, false);
338
440
  }
339
441
  }
340
442
 
@@ -342,28 +444,65 @@ const parseXml = function (xmlData) {
342
444
  const lastTag = currentNode;
343
445
  if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
344
446
  currentNode = this.tagsNodeStack.pop();
345
- jPath = jPath.substring(0, jPath.lastIndexOf("."));
447
+ this.matcher.pop();
448
+ }
449
+
450
+ // Clean up self-closing syntax BEFORE processing attributes
451
+ // This is where tagExp gets the trailing / removed
452
+ let isSelfClosing = false;
453
+ if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
454
+ isSelfClosing = true;
455
+ if (tagName[tagName.length - 1] === "/") {
456
+ tagName = tagName.substr(0, tagName.length - 1);
457
+ tagExp = tagName;
458
+ } else {
459
+ tagExp = tagExp.substr(0, tagExp.length - 1);
460
+ }
461
+
462
+ // Re-check attrExpPresent after cleaning
463
+ attrExpPresent = (tagName !== tagExp);
346
464
  }
465
+
466
+ // Now process attributes with CLEAN tagExp (no trailing /)
467
+ let prefixedAttrs = null;
468
+ let rawAttrs = {};
469
+ let namespace = undefined;
470
+
471
+ // Extract namespace from rawTagName
472
+ namespace = extractNamespace(rawTagName);
473
+
474
+ // Push tag to matcher FIRST (with empty attrs for now) so callbacks see correct path
347
475
  if (tagName !== xmlObj.tagname) {
348
- jPath += jPath ? "." + tagName : tagName;
476
+ this.matcher.push(tagName, {}, namespace);
349
477
  }
478
+
479
+ // Now build attributes - callbacks will see correct matcher state
480
+ if (tagName !== tagExp && attrExpPresent) {
481
+ // Build attributes (returns prefixed attributes for the tree)
482
+ // Note: buildAttributesMap now internally updates the matcher with raw attributes
483
+ prefixedAttrs = this.buildAttributesMap(tagExp, this.matcher, tagName);
484
+
485
+ if (prefixedAttrs) {
486
+ // Extract raw attributes (without prefix) for our use
487
+ rawAttrs = extractRawAttributes(prefixedAttrs, this.options);
488
+ }
489
+ }
490
+
491
+ // Now check if this is a stop node (after attributes are set)
492
+ if (tagName !== xmlObj.tagname) {
493
+ this.isCurrentNodeStopNode = this.isItStopNode(this.stopNodeExpressions, this.matcher);
494
+ }
495
+
350
496
  const startIndex = i;
351
- if (this.isItStopNode(this.stopNodesExact, this.stopNodesWildcard, jPath, tagName)) {
497
+ if (this.isCurrentNodeStopNode) {
352
498
  let tagContent = "";
353
- //self-closing tag
354
- if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
355
- if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
356
- tagName = tagName.substr(0, tagName.length - 1);
357
- jPath = jPath.substr(0, jPath.length - 1);
358
- tagExp = tagName;
359
- } else {
360
- tagExp = tagExp.substr(0, tagExp.length - 1);
361
- }
499
+
500
+ // For self-closing tags, content is empty
501
+ if (isSelfClosing) {
362
502
  i = result.closeIndex;
363
503
  }
364
504
  //unpaired tag
365
505
  else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
366
-
367
506
  i = result.closeIndex;
368
507
  }
369
508
  //normal tag
@@ -377,50 +516,38 @@ const parseXml = function (xmlData) {
377
516
 
378
517
  const childNode = new xmlNode(tagName);
379
518
 
380
- if (tagName !== tagExp && attrExpPresent) {
381
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
382
- }
383
- if (tagContent) {
384
- tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
519
+ if (prefixedAttrs) {
520
+ childNode[":@"] = prefixedAttrs;
385
521
  }
386
522
 
387
- jPath = jPath.substr(0, jPath.lastIndexOf("."));
523
+ // For stop nodes, store raw content as-is without any processing
388
524
  childNode.add(this.options.textNodeName, tagContent);
389
525
 
390
- this.addChild(currentNode, childNode, jPath, startIndex);
526
+ this.matcher.pop(); // Pop the stop node tag
527
+ this.isCurrentNodeStopNode = false; // Reset flag
528
+
529
+ this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
391
530
  } else {
392
531
  //selfClosing tag
393
- if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
394
- if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
395
- tagName = tagName.substr(0, tagName.length - 1);
396
- jPath = jPath.substr(0, jPath.length - 1);
397
- tagExp = tagName;
398
- } else {
399
- tagExp = tagExp.substr(0, tagExp.length - 1);
400
- }
401
-
402
- if (this.options.transformTagName) {
403
- const newTagName = this.options.transformTagName(tagName);
404
- if (tagExp === tagName) {
405
- tagExp = newTagName
406
- }
407
- tagName = newTagName;
408
- }
532
+ if (isSelfClosing) {
533
+ ({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options));
409
534
 
410
535
  const childNode = new xmlNode(tagName);
411
- if (tagName !== tagExp && attrExpPresent) {
412
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
536
+ if (prefixedAttrs) {
537
+ childNode[":@"] = prefixedAttrs;
413
538
  }
414
- this.addChild(currentNode, childNode, jPath, startIndex);
415
- jPath = jPath.substr(0, jPath.lastIndexOf("."));
539
+ this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
540
+ this.matcher.pop(); // Pop self-closing tag
541
+ this.isCurrentNodeStopNode = false; // Reset flag
416
542
  }
417
- else if(this.options.unpairedTags.indexOf(tagName) !== -1){//unpaired tag
543
+ else if (this.options.unpairedTags.indexOf(tagName) !== -1) {//unpaired tag
418
544
  const childNode = new xmlNode(tagName);
419
- if(tagName !== tagExp && attrExpPresent){
420
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
545
+ if (prefixedAttrs) {
546
+ childNode[":@"] = prefixedAttrs;
421
547
  }
422
- this.addChild(currentNode, childNode, jPath, startIndex);
423
- jPath = jPath.substr(0, jPath.lastIndexOf("."));
548
+ this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
549
+ this.matcher.pop(); // Pop unpaired tag
550
+ this.isCurrentNodeStopNode = false; // Reset flag
424
551
  i = result.closeIndex;
425
552
  // Continue to next iteration without changing currentNode
426
553
  continue;
@@ -433,10 +560,10 @@ const parseXml = function (xmlData) {
433
560
  }
434
561
  this.tagsNodeStack.push(currentNode);
435
562
 
436
- if (tagName !== tagExp && attrExpPresent) {
437
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
563
+ if (prefixedAttrs) {
564
+ childNode[":@"] = prefixedAttrs;
438
565
  }
439
- this.addChild(currentNode, childNode, jPath, startIndex);
566
+ this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
440
567
  currentNode = childNode;
441
568
  }
442
569
  textData = "";
@@ -450,10 +577,13 @@ const parseXml = function (xmlData) {
450
577
  return xmlObj.child;
451
578
  }
452
579
 
453
- function addChild(currentNode, childNode, jPath, startIndex) {
580
+ function addChild(currentNode, childNode, matcher, startIndex) {
454
581
  // unset startIndex if not requested
455
582
  if (!this.options.captureMetaData) startIndex = undefined;
456
- const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
583
+
584
+ // Pass jPath string or matcher based on options.jPath setting
585
+ const jPathOrMatcher = this.options.jPath ? matcher.toString() : matcher;
586
+ const result = this.options.updateTag(childNode.tagname, jPathOrMatcher, childNode[":@"])
457
587
  if (result === false) {
458
588
  //do nothing
459
589
  } else if (typeof result === "string") {
@@ -464,33 +594,40 @@ function addChild(currentNode, childNode, jPath, startIndex) {
464
594
  }
465
595
  }
466
596
 
467
- const replaceEntitiesValue = function (val, tagName, jPath) {
468
- // Performance optimization: Early return if no entities to replace
469
- if (val.indexOf('&') === -1) {
470
- return val;
471
- }
472
-
597
+ /**
598
+ * @param {object} val - Entity object with regex and val properties
599
+ * @param {string} tagName - Tag name
600
+ * @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
601
+ */
602
+ function replaceEntitiesValue(val, tagName, jPath) {
473
603
  const entityConfig = this.options.processEntities;
474
604
 
475
- if (!entityConfig.enabled) {
605
+ if (!entityConfig || !entityConfig.enabled) {
476
606
  return val;
477
607
  }
478
608
 
479
- // Check tag-specific filtering
609
+ // Check if tag is allowed to contain entities
480
610
  if (entityConfig.allowedTags) {
481
- if (!entityConfig.allowedTags.includes(tagName)) {
482
- return val; // Skip entity replacement for current tag as not set
611
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
612
+ const allowed = Array.isArray(entityConfig.allowedTags)
613
+ ? entityConfig.allowedTags.includes(tagName)
614
+ : entityConfig.allowedTags(tagName, jPathOrMatcher);
615
+
616
+ if (!allowed) {
617
+ return val;
483
618
  }
484
619
  }
485
620
 
621
+ // Apply custom tag filter if provided
486
622
  if (entityConfig.tagFilter) {
487
- if (!entityConfig.tagFilter(tagName, jPath)) {
623
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
624
+ if (!entityConfig.tagFilter(tagName, jPathOrMatcher)) {
488
625
  return val; // Skip based on custom filter
489
626
  }
490
627
  }
491
628
 
492
629
  // Replace DOCTYPE entities
493
- for (let entityName in this.docTypeEntities) {
630
+ for (const entityName of Object.keys(this.docTypeEntities)) {
494
631
  const entity = this.docTypeEntities[entityName];
495
632
  const matches = val.match(entity.regx);
496
633
 
@@ -522,19 +659,38 @@ const replaceEntitiesValue = function (val, tagName, jPath) {
522
659
  }
523
660
  }
524
661
  }
525
- if (val.indexOf('&') === -1) return val; // Early exit
526
-
527
662
  // Replace standard entities
528
- for (let entityName in this.lastEntities) {
663
+ for (const entityName of Object.keys(this.lastEntities)) {
529
664
  const entity = this.lastEntities[entityName];
665
+ const matches = val.match(entity.regex);
666
+ if (matches) {
667
+ this.entityExpansionCount += matches.length;
668
+ if (entityConfig.maxTotalExpansions &&
669
+ this.entityExpansionCount > entityConfig.maxTotalExpansions) {
670
+ throw new Error(
671
+ `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
672
+ );
673
+ }
674
+ }
530
675
  val = val.replace(entity.regex, entity.val);
531
676
  }
532
- if (val.indexOf('&') === -1) return val; // Early exit
677
+ if (val.indexOf('&') === -1) return val;
533
678
 
534
679
  // Replace HTML entities if enabled
535
680
  if (this.options.htmlEntities) {
536
- for (let entityName in this.htmlEntities) {
681
+ for (const entityName of Object.keys(this.htmlEntities)) {
537
682
  const entity = this.htmlEntities[entityName];
683
+ const matches = val.match(entity.regex);
684
+ if (matches) {
685
+ //console.log(matches);
686
+ this.entityExpansionCount += matches.length;
687
+ if (entityConfig.maxTotalExpansions &&
688
+ this.entityExpansionCount > entityConfig.maxTotalExpansions) {
689
+ throw new Error(
690
+ `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
691
+ );
692
+ }
693
+ }
538
694
  val = val.replace(entity.regex, entity.val);
539
695
  }
540
696
  }
@@ -546,13 +702,13 @@ const replaceEntitiesValue = function (val, tagName, jPath) {
546
702
  }
547
703
 
548
704
 
549
- function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) {
705
+ function saveTextToParentTag(textData, parentNode, matcher, isLeafNode) {
550
706
  if (textData) { //store previously collected data as textNode
551
707
  if (isLeafNode === undefined) isLeafNode = parentNode.child.length === 0
552
708
 
553
709
  textData = this.parseTextData(textData,
554
710
  parentNode.tagname,
555
- jPath,
711
+ matcher,
556
712
  false,
557
713
  parentNode[":@"] ? Object.keys(parentNode[":@"]).length !== 0 : false,
558
714
  isLeafNode);
@@ -566,14 +722,17 @@ function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) {
566
722
 
567
723
  //TODO: use jPath to simplify the logic
568
724
  /**
569
- * @param {Set} stopNodesExact
570
- * @param {Set} stopNodesWildcard
571
- * @param {string} jPath
572
- * @param {string} currentTagName
725
+ * @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
726
+ * @param {Matcher} matcher - Current path matcher
573
727
  */
574
- function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName) {
575
- if (stopNodesWildcard && stopNodesWildcard.has(currentTagName)) return true;
576
- if (stopNodesExact && stopNodesExact.has(jPath)) return true;
728
+ function isItStopNode(stopNodeExpressions, matcher) {
729
+ if (!stopNodeExpressions || stopNodeExpressions.length === 0) return false;
730
+
731
+ for (let i = 0; i < stopNodeExpressions.length; i++) {
732
+ if (matcher.matches(stopNodeExpressions[i])) {
733
+ return true;
734
+ }
735
+ }
577
736
  return false;
578
737
  }
579
738
 
@@ -726,4 +885,27 @@ function fromCodePoint(str, base, prefix) {
726
885
  } else {
727
886
  return prefix + str + ";";
728
887
  }
888
+ }
889
+
890
+ function transformTagName(fn, tagName, tagExp, options) {
891
+ if (fn) {
892
+ const newTagName = fn(tagName);
893
+ if (tagExp === tagName) {
894
+ tagExp = newTagName
895
+ }
896
+ tagName = newTagName;
897
+ }
898
+ tagName = sanitizeName(tagName, options);
899
+ return { tagName, tagExp };
900
+ }
901
+
902
+
903
+
904
+ function sanitizeName(name, options) {
905
+ if (criticalProperties.includes(name)) {
906
+ throw new Error(`[SECURITY] Invalid name: "${name}" is a reserved JavaScript keyword that could cause prototype pollution`);
907
+ } else if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
908
+ return options.onDangerousProperty(name);
909
+ }
910
+ return name;
729
911
  }