• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NaturalIntelligence / fast-xml-parser / 24500125020

16 Apr 2026 08:26AM UTC coverage: 97.64% (+0.02%) from 97.617%
24500125020

push

github

amitguptagwl
remove unwanted tests

1135 of 1182 branches covered (96.02%)

9392 of 9619 relevant lines covered (97.64%)

463944.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.25
/src/xmlparser/OptionsBuilder.js
1
import { DANGEROUS_PROPERTY_NAMES, criticalProperties } from "../util.js";
5✔
2
import { COMMON_HTML, CURRENCY } from '@nodable/entities';
5✔
3

5✔
4
const defaultOnDangerousProperty = (name) => {
5✔
5
  if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
×
6
    return "__" + name;
×
7
  }
×
8
  return name;
×
9
};
5✔
10

5✔
11

5✔
12
export const defaultOptions = {
5✔
13
  preserveOrder: false,
5✔
14
  attributeNamePrefix: '@_',
5✔
15
  attributesGroupName: false,
5✔
16
  textNodeName: '#text',
5✔
17
  ignoreAttributes: true,
5✔
18
  removeNSPrefix: false, // remove NS from tag name or attribute name if true
5✔
19
  allowBooleanAttributes: false, //a tag can have attributes without any value
5✔
20
  //ignoreRootElement : false,
5✔
21
  parseTagValue: true,
5✔
22
  parseAttributeValue: false,
5✔
23
  trimValues: true, //Trim string values of tag and attributes
5✔
24
  cdataPropName: false,
5✔
25
  numberParseOptions: {
5✔
26
    hex: true,
5✔
27
    leadingZeros: true,
5✔
28
    eNotation: true
5✔
29
  },
5✔
30
  tagValueProcessor: function (tagName, val) {
5✔
31
    return val;
1,840✔
32
  },
5✔
33
  attributeValueProcessor: function (attrName, val) {
5✔
34
    return val;
1,230✔
35
  },
5✔
36
  stopNodes: [], //nested tags will not be parsed even for errors
5✔
37
  alwaysCreateTextNode: false,
5✔
38
  isArray: () => false,
5✔
39
  commentPropName: false,
5✔
40
  unpairedTags: [],
5✔
41
  processEntities: true,
5✔
42
  htmlEntities: false,
5✔
43
  entityDecoder: null,
5✔
44
  ignoreDeclaration: false,
5✔
45
  ignorePiTags: false,
5✔
46
  transformTagName: false,
5✔
47
  transformAttributeName: false,
5✔
48
  updateTag: function (tagName, jPath, attrs) {
5✔
49
    return tagName
6,380✔
50
  },
5✔
51
  // skipEmptyListItem: false
5✔
52
  captureMetaData: false,
5✔
53
  maxNestedTags: 100,
5✔
54
  strictReservedNames: true,
5✔
55
  jPath: true, // if true, pass jPath string to callbacks; if false, pass matcher instance
5✔
56
  onDangerousProperty: defaultOnDangerousProperty
5✔
57
};
5✔
58

5✔
59

5✔
60
/**
5✔
61
 * Validates that a property name is safe to use
5✔
62
 * @param {string} propertyName - The property name to validate
5✔
63
 * @param {string} optionName - The option field name (for error message)
5✔
64
 * @throws {Error} If property name is dangerous
5✔
65
 */
5✔
66
function validatePropertyName(propertyName, optionName) {
2,400✔
67
  if (typeof propertyName !== 'string') {
2,400!
68
    return; // Only validate string property names
×
69
  }
×
70

2,400✔
71
  const normalized = propertyName.toLowerCase();
2,400✔
72
  if (DANGEROUS_PROPERTY_NAMES.some(dangerous => normalized === dangerous.toLowerCase())) {
2,400✔
73
    throw new Error(
35✔
74
      `[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution`
35✔
75
    );
35✔
76
  }
35✔
77

2,365✔
78
  if (criticalProperties.some(dangerous => normalized === dangerous.toLowerCase())) {
2,400✔
79
    throw new Error(
15✔
80
      `[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution`
15✔
81
    );
15✔
82
  }
15✔
83
}
2,400✔
84

5✔
85
/**
5✔
86
 * Normalizes processEntities option for backward compatibility
5✔
87
 * @param {boolean|object} value 
5✔
88
 * @returns {object} Always returns normalized object
5✔
89
 */
5✔
90
function normalizeProcessEntities(value, htmlEntities) {
1,225✔
91
  // Boolean backward compatibility
1,225✔
92
  if (typeof value === 'boolean') {
1,225✔
93
    return {
1,100✔
94
      enabled: value, // true or false
1,100✔
95
      maxEntitySize: 10000,
1,100✔
96
      maxExpansionDepth: 10000,
1,100✔
97
      maxTotalExpansions: Infinity,
1,100✔
98
      maxExpandedLength: 100000,
1,100✔
99
      maxEntityCount: 1000,
1,100✔
100
      allowedTags: null,
1,100✔
101
      tagFilter: null,
1,100✔
102
      appliesTo: "all",
1,100✔
103
    };
1,100✔
104
  }
1,100✔
105

125✔
106
  // Object config - merge with defaults
125✔
107
  if (typeof value === 'object' && value !== null) {
1,225✔
108
    return {
125✔
109
      enabled: value.enabled !== false,
125✔
110
      maxEntitySize: Math.max(1, value.maxEntitySize ?? 10000),
125✔
111
      maxExpansionDepth: Math.max(1, value.maxExpansionDepth ?? 10000),
125✔
112
      maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? Infinity),
125✔
113
      maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000),
125✔
114
      maxEntityCount: Math.max(1, value.maxEntityCount ?? 1000),
125✔
115
      allowedTags: value.allowedTags ?? null,
125✔
116
      tagFilter: value.tagFilter ?? null,
125✔
117
      appliesTo: value.appliesTo ?? "all",
125✔
118
    };
125✔
119
  }
125✔
120

×
121
  // Default to enabled with limits
×
122
  return normalizeProcessEntities(true);
×
123
}
1,225✔
124

5✔
125
export const buildOptions = function (options) {
5✔
126
  const built = Object.assign({}, defaultOptions, options);
1,275✔
127

1,275✔
128
  // Validate property names to prevent prototype pollution
1,275✔
129
  const propertyNameOptions = [
1,275✔
130
    { value: built.attributeNamePrefix, name: 'attributeNamePrefix' },
1,275✔
131
    { value: built.attributesGroupName, name: 'attributesGroupName' },
1,275✔
132
    { value: built.textNodeName, name: 'textNodeName' },
1,275✔
133
    { value: built.cdataPropName, name: 'cdataPropName' },
1,275✔
134
    { value: built.commentPropName, name: 'commentPropName' }
1,275✔
135
  ];
1,275✔
136

1,275✔
137
  for (const { value, name } of propertyNameOptions) {
1,275✔
138
    if (value) {
6,275✔
139
      validatePropertyName(value, name);
2,400✔
140
    }
2,400✔
141
  }
6,275✔
142

1,225✔
143
  if (built.onDangerousProperty === null) {
1,275!
144
    built.onDangerousProperty = defaultOnDangerousProperty;
×
145
  }
×
146

1,225✔
147
  // Always normalize processEntities for backward compatibility and validation
1,225✔
148
  built.processEntities = normalizeProcessEntities(built.processEntities, built.htmlEntities);
1,225✔
149
  built.unpairedTagsSet = new Set(built.unpairedTags);
1,225✔
150
  // Convert old-style stopNodes for backward compatibility
1,225✔
151
  if (built.stopNodes && Array.isArray(built.stopNodes)) {
1,275✔
152
    built.stopNodes = built.stopNodes.map(node => {
1,225✔
153
      if (typeof node === 'string' && node.startsWith('*.')) {
265✔
154
        // Old syntax: *.tagname meant "tagname anywhere"
60✔
155
        // Convert to new syntax: ..tagname
60✔
156
        return '..' + node.substring(2);
60✔
157
      }
60✔
158
      return node;
205✔
159
    });
1,225✔
160
  }
1,225✔
161
  //console.debug(built.processEntities)
1,225✔
162
  return built;
1,225✔
163
};
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc