@mitre/inspec-objects 2.0.0 → 2.0.1

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.
@@ -99,7 +99,7 @@ function processOVAL(oval) {
99
99
  if (!oval) {
100
100
  return undefined;
101
101
  }
102
- const parsed = (0, xccdf_1.convertEncodedXmlIntoJson)(oval);
102
+ const parsed = (0, xccdf_1.convertEncodedXmlIntoJson)(oval, 'withArrayNoEntitiesOption');
103
103
  const extractedDefinitions = {};
104
104
  for (const ovalDefinitions of parsed.oval_definitions) {
105
105
  for (const definitionList of ovalDefinitions.definitions) {
@@ -28,7 +28,11 @@ export type InputTextLang = {
28
28
  };
29
29
  /**
30
30
  * Processes an XCCDF XML string and converts it into a Profile object.
31
- * Note: Moved the newline removal to diff library rather than here.
31
+ * NOTE: We are using the fast xml parser (FXP) V4 which requires to specify
32
+ * which Whether a single tag should be parsed as an array or an object,
33
+ * it can't be decided by FXP. We process every tag as an array, this is
34
+ * the reason there are numerous tag test, were array index zero [0] is
35
+ * tested.
32
36
  *
33
37
  * @param xml - The XCCDF XML string to process.
34
38
  * @param removeNewlines - A flag indicating whether to remove newlines from the processed data.
@@ -55,21 +55,32 @@ function extractAllComplexChecks(complexCheck) {
55
55
  return complexChecks;
56
56
  }
57
57
  /**
58
- * Ensures that the input is decoded as an XML string value.
58
+ * Ensures that the input is decoded to a string value.
59
59
  *
60
- * @param input - The input value which can be either a string or an array of
61
- * InputTextLang objects.
62
- * @param defaultValue - The default string value to return if the input is
63
- * not a string.
64
- * @returns The decoded XML string value if the input is a string, otherwise the
65
- * value from the first element of the input array or the default value.
60
+ * This function takes an input which can be either a string or an array of `InputTextLang` objects.
61
+ * If the input is a string, it returns the input as is.
62
+ * If the input is an array, it attempts to retrieve the `#text` property from the first element of the array.
63
+ * If the input is neither a string nor an array, it attempts to retrieve the `#text` property from the input.
64
+ * If the `#text` property is not found, it returns the provided default value.
65
+ *
66
+ * @param input - The input value which can be a string or an array of `InputTextLang` objects.
67
+ * @param defaultValue - The default value to return if the `#text` property is not found.
68
+ * @returns The decoded string value or the default value.
66
69
  */
67
70
  function ensureDecodedXMLStringValue(input, defaultValue) {
68
- return lodash_1.default.isString(input) ? input : lodash_1.default.get(input, '[0].#text', defaultValue);
71
+ return lodash_1.default.isString(input)
72
+ ? input
73
+ : lodash_1.default.isArray(input)
74
+ ? lodash_1.default.get(input, '[0].#text', defaultValue)
75
+ : lodash_1.default.get(input, '#text', defaultValue);
69
76
  }
70
77
  /**
71
78
  * Processes an XCCDF XML string and converts it into a Profile object.
72
- * Note: Moved the newline removal to diff library rather than here.
79
+ * NOTE: We are using the fast xml parser (FXP) V4 which requires to specify
80
+ * which Whether a single tag should be parsed as an array or an object,
81
+ * it can't be decided by FXP. We process every tag as an array, this is
82
+ * the reason there are numerous tag test, were array index zero [0] is
83
+ * tested.
73
84
  *
74
85
  * @param xml - The XCCDF XML string to process.
75
86
  * @param removeNewlines - A flag indicating whether to remove newlines from the processed data.
@@ -84,14 +95,40 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
84
95
  if (parsedXML.Benchmark === undefined) {
85
96
  throw new Error('Could not process the XCCDF file, check the input to make sure this is a properly formatted XCCDF file.');
86
97
  }
98
+ // Extracts all rules from the given benchmark groups.
87
99
  const rules = extractAllRules(parsedXML.Benchmark[0].Group);
100
+ // Variable used to store the profile data.
101
+ // The name is the benchmark Id, title and summary are from benchmark.
88
102
  const profile = new profile_1.default({
89
- name: parsedXML.Benchmark[0]['@_id'],
90
- title: parsedXML.Benchmark[0].title[0]['#text'],
91
- summary: parsedXML.Benchmark[0].description[0]['#text']
103
+ //name: parsedXML.Benchmark[0]['@_id'],
104
+ // title: (parsedXML.Benchmark[0].title[0] as FrontMatter)['#text'],
105
+ // summary: (parsedXML.Benchmark[0].description[0] as RationaleElement)['#text']
106
+ name: Array.isArray(parsedXML.Benchmark[0]['@_id'])
107
+ ? parsedXML.Benchmark[0]['@_id'].map(n => n['@_id']).join(' ') === ''
108
+ ? parsedXML.Benchmark[0]['@_id'].map(n => n).join(' ')
109
+ : parsedXML.Benchmark[0]['@_id'].join(' ')
110
+ : parsedXML.Benchmark[0]['@_id'],
111
+ title: Array.isArray(parsedXML.Benchmark[0].title)
112
+ ? parsedXML.Benchmark[0].title.map(t => t['#text']).join(' ') === ''
113
+ ? parsedXML.Benchmark[0].title.map(t => t).join(' ')
114
+ : parsedXML.Benchmark[0].title.map(t => t['#text']).join(' ')
115
+ : parsedXML.Benchmark[0].title,
116
+ summary: Array.isArray(parsedXML.Benchmark[0].description)
117
+ ? parsedXML.Benchmark[0].description.map(d => d['#text']).join(' ') === ''
118
+ ? parsedXML.Benchmark[0].description.map(d => d['p'] || '').join(' ') === ''
119
+ ? parsedXML.Benchmark[0].description.map(d => d).join(' ')
120
+ : parsedXML.Benchmark[0].description.map(d => d['p'] || '').join(' ')
121
+ : parsedXML.Benchmark[0].description.map(d => d['#text']).join(' ')
122
+ : parsedXML.Benchmark[0].description
92
123
  });
124
+ // Process each rule, extracting the necessary
125
+ // data and save it to the profile variable.
93
126
  rules.forEach(rule => {
94
127
  var _a, _b, _c;
128
+ // The description tag contains the following tags:
129
+ // "FalsePositives", "FalseNegatives", "Documentable", "Mitigations",
130
+ // "SeverityOverrideGuidance", "PotentialImpacts", "ThirdPartyTools",
131
+ // "MitigationControl", "Responsibility", "IAControls"
95
132
  let extractedDescription;
96
133
  if (typeof rule.description === 'object') {
97
134
  if (Array.isArray(rule.description) && lodash_1.default.get(rule, "description[0]['#text']")) {
@@ -105,6 +142,10 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
105
142
  if (Array.isArray(lodash_1.default.get(rule.description, '[0].p'))) {
106
143
  const joinedDescriptions = lodash_1.default.get(rule.description, '[0].p');
107
144
  extractedDescription = (0, pretty_1.default)(joinedDescriptions.join('\n\n'));
145
+ extractedDescription = (0, xccdf_1.removeHtmlTags)(extractedDescription).replace('\n', ' ');
146
+ }
147
+ else if (Array.isArray(rule.description)) {
148
+ extractedDescription = (0, xccdf_1.convertEncodedHTMLIntoJson)(rule.description[0]);
108
149
  }
109
150
  else {
110
151
  extractedDescription = JSON.stringify(rule.description);
@@ -115,10 +156,12 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
115
156
  else {
116
157
  extractedDescription = (0, xccdf_1.convertEncodedHTMLIntoJson)(rule.description);
117
158
  }
159
+ // Create a new control object and populate it with the necessary data.
118
160
  const control = new control_1.default();
161
+ // Update the control Id with the appropriate value based on the rule id.
119
162
  switch (useRuleId) {
120
163
  case 'group':
121
- control.id = rule.group['@_id'];
164
+ control.id = rule.group['@_id'].toString();
122
165
  break;
123
166
  case 'rule':
124
167
  if (rule['@_id'][0].toLowerCase().startsWith('sv')) {
@@ -129,27 +172,51 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
129
172
  }
130
173
  break;
131
174
  case 'version':
132
- control.id = rule.version;
175
+ if (rule.version !== undefined) {
176
+ (lodash_1.default.isArray(rule.version))
177
+ ? control.id = rule.version[0]
178
+ : control.id = rule.version;
179
+ }
180
+ else {
181
+ throw new Error('The rule type "version" did not provide an identification (Id) value');
182
+ }
133
183
  break;
134
- case 'cis':
135
- // eslint-disable-next-line no-case-declarations
184
+ case 'cis': {
185
+ // Regex explained
186
+ // \d:
187
+ // matches a single digit (0-9), the required starting point of the match.
188
+ // (\d?):
189
+ // matches an optional digit, there are three of these in sequence
190
+ // (.\d(\d?)(\d?)(\d?))?:
191
+ // matches an optional group that starts with a period (.) followed
192
+ // by one digit and up to three additional optional digits
193
+ // The pattern is repeated four times to match between zero and four
194
+ // groups of a period followed by one required digit and up to three
195
+ // additional optional digits. The pattern matches:
196
+ // 1, 123, 1.2, 1.234, 1.2.3.4.5, or 1.23.456.7.89
136
197
  const controlIdRegex = /\d(\d?)(\d?)(\d?)(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?/g;
137
- // eslint-disable-next-line no-case-declarations
138
198
  const controlIdMatch = controlIdRegex.exec(rule['@_id']);
139
199
  if (controlIdMatch) {
140
200
  control.id = controlIdMatch[0];
141
201
  }
142
202
  else {
143
- throw new Error(`Could not parse control ID from rule ID: ${rule['@_id']}. Expecting something in this example format: 'xccdf_org.cisecurity.benchmarks_rule_1.1.11_Rule_title_summary`);
203
+ throw new Error(`Could not parse control ID from rule ID: ${rule['@_id']}. Expecting something in this example format: xccdf_org.cisecurity.benchmarks_rule_1.1.11_Rule_title_summary`);
144
204
  }
145
205
  break;
206
+ }
146
207
  default:
147
- throw new Error('useRuleId must be one of "group", "rule", or "version"');
208
+ throw new Error('useRuleId must be one of "group", "rule", "version" for STIG benchmarks, or "cis" for CIS benchmarks');
148
209
  }
149
210
  if (!(lodash_1.default.isArray(rule.title) && rule.title.length === 1)) {
150
211
  throw new Error('Rule title is not an array of length 1. Investigate if the file is in the proper format.');
151
212
  }
152
- control.title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] ? ensureDecodedXMLStringValue(rule.title[0], 'undefined title') : `[[[MISSING SEVERITY FROM BENCHMARK]]] ${ensureDecodedXMLStringValue(rule.title[0], 'undefined title')}`);
213
+ // Update the control title with the rule.tile content if a rule severity
214
+ // exists after removing any special characters, otherwise set the control
215
+ // title to [[[MISSING SEVERITY FROM BENCHMARK]]], undefined title.
216
+ control.title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] || rule['@_weight']
217
+ ? ensureDecodedXMLStringValue(rule.title[0], 'undefined title')
218
+ : `[[[MISSING SEVERITY or WEIGHT FROM BENCHMARK]]] ${ensureDecodedXMLStringValue(rule.title[0], 'undefined title')}`);
219
+ // Update the control description (desc) with the extracted description content
153
220
  if (typeof extractedDescription === 'object' && !Array.isArray(extractedDescription)) {
154
221
  control.desc = ((_a = extractedDescription.VulnDiscussion) === null || _a === void 0 ? void 0 : _a.split('Satisfies: ')[0]) || '';
155
222
  }
@@ -162,10 +229,13 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
162
229
  else {
163
230
  logger.warn(`Invalid value for extracted description: ${extractedDescription}`);
164
231
  }
232
+ // Update the control impact with the severity value from the rule,
233
+ // default to medium (0.5) if not found.
165
234
  control.impact = (0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'medium');
166
235
  if (!control.descs || Array.isArray(control.descs)) {
167
236
  control.descs = {};
168
237
  }
238
+ // Update the control descriptions (descs) check with the check text from the rule,
169
239
  if (rule.check) {
170
240
  if (rule.check.some((ruleValue) => 'check-content' in ruleValue)) {
171
241
  control.descs.check = (0, xccdf_1.removeXMLSpecialCharacters)(rule.check ? rule.check[0]['check-content'][0] : 'Missing description');
@@ -240,20 +310,22 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
240
310
  control.descs.check = checkTexts.join('\n');
241
311
  }
242
312
  }
313
+ // Update the control descriptions (descs) fix with content from the rule
314
+ // fixtest, if not found, defaults to "Missing fix text"
243
315
  if (lodash_1.default.get(rule.fixtext, '[0]["#text"]')) {
244
316
  control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext[0]['#text']);
245
317
  }
246
318
  else if (typeof rule.fixtext === 'undefined') {
247
319
  if (rule.fix && rule.fix[0]) {
248
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fix[0]['#text'] || 'Missing fix text');
320
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)(rule.fix[0]['#text'] || 'Missing fix text');
249
321
  }
250
322
  }
251
323
  else if (typeof rule.fixtext[0] === 'string') {
252
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext[0]);
324
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)(rule.fixtext[0]);
253
325
  }
254
326
  else if (typeof rule.fixtext[0] === 'object') {
255
327
  if (Array.isArray(rule.fixtext[0])) {
256
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext[0].map((fixtext) => {
328
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext[0].map((fixtext) => {
257
329
  if (fixtext.div) {
258
330
  return fixtext.div;
259
331
  }
@@ -263,21 +335,25 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
263
335
  }))));
264
336
  }
265
337
  else {
266
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext)));
338
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)((0, xccdf_1.removeXMLSpecialCharacters)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext)))).replace('\n', ' ').trim();
267
339
  }
268
340
  }
269
341
  else {
270
342
  control.descs.fix = 'Missing fix text';
271
343
  }
344
+ // Update the control tags base on corresponding rule tags.
272
345
  control.tags.severity = (0, xccdf_1.impactNumberToSeverityString)((0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'medium'));
273
346
  control.tags.gid = rule.group['@_id'],
274
347
  control.tags.rid = rule['@_id'];
275
348
  control.tags.stig_id = rule['version'];
276
- if (typeof rule.group.title[0] === 'string') {
277
- control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(rule.group.title[0]);
349
+ if (typeof rule.group.title === 'string') {
350
+ control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(rule.group.title);
278
351
  }
279
352
  else {
280
- control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(lodash_1.default.get(rule.group, 'title[0].#text', 'undefined title'));
353
+ const gtitle = lodash_1.default.get(rule.group, 'title[0].#text', 'undefined title') === 'undefined title'
354
+ ? lodash_1.default.get(rule.group, 'title[0]', 'undefined title')
355
+ : lodash_1.default.get(rule.group, 'title[0].#text', 'undefined title');
356
+ control.tags.gtitle = typeof gtitle === 'string' ? gtitle : gtitle['#text'] || 'undefined title';
281
357
  }
282
358
  if (rule['fix'] && rule['fix'].length > 0) {
283
359
  control.tags.fix_id = rule['fix'][0]['@_id'];
@@ -285,11 +361,20 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
285
361
  if (rule['rationale']) {
286
362
  control.tags.rationale = rule['rationale'][0]['#text'];
287
363
  }
364
+ // The description tag contains the following tags as well:
365
+ // "FalsePositives", "FalseNegatives", "Documentable", "Mitigations",
366
+ // "SeverityOverrideGuidance", "PotentialImpacts", "ThirdPartyTools",
367
+ // "MitigationControl", "Responsibility", "IAControls"
288
368
  if (typeof extractedDescription === 'object') {
289
- control.tags.satisfies = ((_b = extractedDescription.VulnDiscussion) === null || _b === void 0 ? void 0 : _b.includes('Satisfies: ')) && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined;
369
+ control.tags.satisfies =
370
+ ((_b = extractedDescription.VulnDiscussion) === null || _b === void 0 ? void 0 : _b.includes('Satisfies: ')) && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1
371
+ ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim())
372
+ : undefined;
290
373
  control.tags.false_negatives = extractedDescription.FalseNegatives || undefined;
291
374
  control.tags.false_positives = extractedDescription.FalsePositives || undefined;
292
- control.tags.documentable = typeof extractedDescription.Documentable === 'boolean' ? extractedDescription.Documentable : undefined;
375
+ control.tags.documentable = typeof extractedDescription.Documentable === 'boolean'
376
+ ? extractedDescription.Documentable
377
+ : undefined;
293
378
  control.tags.mitigations = extractedDescription.Mitigations || undefined;
294
379
  control.tags.severity_override_guidance = extractedDescription.SeverityOverrideGuidance || undefined;
295
380
  control.tags.potential_impacts = extractedDescription.PotentialImpacts || undefined;
@@ -299,11 +384,15 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
299
384
  control.tags.responsibility = extractedDescription.Responsibility || undefined;
300
385
  control.tags.ia_controls = extractedDescription.IAControls || undefined;
301
386
  }
387
+ // Ensure that tags inside the tags array are not an array
302
388
  control.tags = lodash_1.default.mapValues(lodash_1.default.omitBy(control.tags, (value) => value === undefined), (value) => {
303
389
  if (value && Array.isArray(value)) {
304
390
  if (Array.isArray(value[0])) {
305
391
  return (0, xccdf_1.removeXMLSpecialCharacters)(value[0][0]);
306
392
  }
393
+ else if (value.length > 1) {
394
+ return value;
395
+ }
307
396
  else {
308
397
  return (0, xccdf_1.removeXMLSpecialCharacters)(value[0]);
309
398
  }
@@ -315,7 +404,7 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
315
404
  return value;
316
405
  }
317
406
  });
318
- // Get all identifiers from the rule
407
+ // Get all identifiers from the rule; cci, nist, and legacy
319
408
  if (rule.ident) {
320
409
  rule.ident.forEach((identifier) => {
321
410
  var _a, _b, _c;
@@ -342,8 +431,9 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
342
431
  }
343
432
  });
344
433
  }
434
+ // Update control references with content from the benchmark rule object
345
435
  (_c = rule.reference) === null || _c === void 0 ? void 0 : _c.forEach((reference) => {
346
- var _a, _b, _c, _d;
436
+ var _a, _b, _c, _d, _e;
347
437
  if (lodash_1.default.get(reference, '@_href') === '') {
348
438
  (_a = control.refs) === null || _a === void 0 ? void 0 : _a.push(lodash_1.default.get(reference, '#text', 'undefined href'));
349
439
  }
@@ -368,7 +458,13 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
368
458
  }
369
459
  else {
370
460
  if ('title' in reference) {
371
- (_d = control.refs) === null || _d === void 0 ? void 0 : _d.push(lodash_1.default.get(reference, 'title'));
461
+ const title = lodash_1.default.get(reference, 'title');
462
+ if (Array.isArray(title)) {
463
+ (_d = control.refs) === null || _d === void 0 ? void 0 : _d.push(title[0]);
464
+ }
465
+ else {
466
+ (_e = control.refs) === null || _e === void 0 ? void 0 : _e.push(lodash_1.default.get(reference, 'title'));
467
+ }
372
468
  }
373
469
  }
374
470
  // Add the reference to the control tags when separated by §
@@ -151,7 +151,7 @@ function diffProfile(fromProfile, toProfile, logger) {
151
151
  logger.error(`Unable to find existing control ${diffValue[1]}`);
152
152
  }
153
153
  }
154
- else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toString().toLowerCase()) && diffValue[1]) {
154
+ else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toLowerCase()) && diffValue[1]) {
155
155
  logger.info(JSON.stringify(diffValue));
156
156
  logger.info(JSON.stringify(changedControlIds));
157
157
  profileDiff.addedControlIDs.push(diffValue[1]);
@@ -285,7 +285,7 @@ function getExistingDescribeFromControl(control) {
285
285
  function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
286
286
  // Try to match based on IDs
287
287
  let updatedControl = updatedControls.find((updatedControl) => {
288
- return updatedControl.id[0].toLowerCase() === existingControl.id[0].toLowerCase();
288
+ return updatedControl.id.toLowerCase() === existingControl.id.toLowerCase();
289
289
  });
290
290
  if (updatedControl) {
291
291
  return updatedControl;
@@ -1,23 +1,55 @@
1
1
  import { DecodedDescription } from '../types/xccdf';
2
2
  /**
3
- * Converts an encoded XML string into a JSON object.
3
+ * Converts an encoded XML string into a JSON object using specified
4
+ * parsing options.
4
5
  *
5
- * @param encodedXml - The encoded XML string to be converted.
6
+ * @param encodedXml - The encoded XML string to be converted.
7
+ * @param xmlParserOption - The parsing option to be used. Defaults to
8
+ * 'withArrayOption'.
9
+ * Possible values are:
10
+ * - 'withArrayOption': Parses XML with array option enabled.
11
+ * - 'withArrayNoEntitiesOption': Parses XML with array option
12
+ * enabled and processes entities.
13
+ * - Any other value: Parses XML without array option.
6
14
  * @returns The JSON representation of the XML string.
7
15
  *
8
16
  * @remarks
9
17
  * This function uses the `fast-xml-parser` library to parse the XML string.
10
18
  * The parser options are configured to:
11
- * - Not ignore attributes.
19
+ * - Prevent the parser from converting XML entities (converting &lt into <)
20
+ * - Ignore attributes, allow or disallows attributes to be parsed
12
21
  * - Remove namespace prefixes.
13
22
  * - Prefix attribute names with '@_'.
14
- * - Stop parsing at 'div' and 'p' nodes.
15
- * - Treat all nodes as arrays.
23
+ * - Stop parsing 'div' and 'p' tags.
24
+ * - Treat all nodes as arrays or not
25
+ *
26
+ * Options being used for the XML parser (V4) are:
27
+ * - processEntities: true or false (based on xmlParserOption)
28
+ * - ignoreAttributes: false (allow attributes to be parsed)
29
+ * - removeNSPrefix: true (remove namespace prefixes)
30
+ * - attributeNamePrefix: '@_' (prefix all attribute names with @_)
31
+ * - stopNodes: ["*.pre", "*.p"]
32
+ * - isArray(): true or false (based on xmlParserOption)
33
+ *
34
+ * NOTE: The isArray can specify what tags to always convert into an array, we
35
+ * do not specify specific fields as it could break parsing if future
36
+ * fields are added, we parse all fields as an array.
16
37
  *
17
38
  * For more details on the parser options, see the documentation for the v4 or v5 version of the library:
18
39
  * {@link https://github.com/NaturalIntelligence/fast-xml-parser/tree/master/docs/v4}
19
40
  */
20
- export declare function convertEncodedXmlIntoJson(encodedXml: string): any;
41
+ /**
42
+ * Converts an encoded XML string into a JSON object using specified parsing options.
43
+ *
44
+ * @param encodedXml - The encoded XML string to be converted.
45
+ * @param xmlParserOption - The parsing option to be used. Defaults to 'withArrayOption'.
46
+ * Possible values are:
47
+ * - 'withArrayOption': Parses XML with array option enabled.
48
+ * - 'withArrayNoEntitiesOption': Parses XML with array option enabled and processes entities.
49
+ * - Any other value: Parses XML without array option.
50
+ * @returns The JSON object resulting from the XML parsing.
51
+ */
52
+ export declare function convertEncodedXmlIntoJson(encodedXml: string, xmlParserOption?: string): any;
21
53
  /**
22
54
  * Converts a JSON object into an XML string.
23
55
  *
@@ -35,6 +67,13 @@ export declare function convertJsonIntoXML(data: any): string;
35
67
  * @returns The decoded string with XML special characters removed.
36
68
  */
37
69
  export declare function removeXMLSpecialCharacters(str: string): string;
70
+ /**
71
+ * Removes HTML tags from the given input string.
72
+ *
73
+ * @param input - The string from which HTML tags should be removed.
74
+ * @returns A new string with all HTML tags removed.
75
+ */
76
+ export declare function removeHtmlTags(input: string): string;
38
77
  /**
39
78
  * Converts a severity string to a numerical impact value.
40
79
  *
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertEncodedXmlIntoJson = convertEncodedXmlIntoJson;
4
4
  exports.convertJsonIntoXML = convertJsonIntoXML;
5
5
  exports.removeXMLSpecialCharacters = removeXMLSpecialCharacters;
6
+ exports.removeHtmlTags = removeHtmlTags;
6
7
  exports.severityStringToImpact = severityStringToImpact;
7
8
  exports.impactNumberToSeverityString = impactNumberToSeverityString;
8
9
  exports.convertEncodedHTMLIntoJson = convertEncodedHTMLIntoJson;
@@ -13,33 +14,85 @@ const htmlparser = tslib_1.__importStar(require("htmlparser2"));
13
14
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
14
15
  const he_1 = tslib_1.__importDefault(require("he"));
15
16
  /**
16
- * Converts an encoded XML string into a JSON object.
17
+ * Converts an encoded XML string into a JSON object using specified
18
+ * parsing options.
17
19
  *
18
- * @param encodedXml - The encoded XML string to be converted.
20
+ * @param encodedXml - The encoded XML string to be converted.
21
+ * @param xmlParserOption - The parsing option to be used. Defaults to
22
+ * 'withArrayOption'.
23
+ * Possible values are:
24
+ * - 'withArrayOption': Parses XML with array option enabled.
25
+ * - 'withArrayNoEntitiesOption': Parses XML with array option
26
+ * enabled and processes entities.
27
+ * - Any other value: Parses XML without array option.
19
28
  * @returns The JSON representation of the XML string.
20
29
  *
21
30
  * @remarks
22
31
  * This function uses the `fast-xml-parser` library to parse the XML string.
23
32
  * The parser options are configured to:
24
- * - Not ignore attributes.
33
+ * - Prevent the parser from converting XML entities (converting &lt into <)
34
+ * - Ignore attributes, allow or disallows attributes to be parsed
25
35
  * - Remove namespace prefixes.
26
36
  * - Prefix attribute names with '@_'.
27
- * - Stop parsing at 'div' and 'p' nodes.
28
- * - Treat all nodes as arrays.
37
+ * - Stop parsing 'div' and 'p' tags.
38
+ * - Treat all nodes as arrays or not
39
+ *
40
+ * Options being used for the XML parser (V4) are:
41
+ * - processEntities: true or false (based on xmlParserOption)
42
+ * - ignoreAttributes: false (allow attributes to be parsed)
43
+ * - removeNSPrefix: true (remove namespace prefixes)
44
+ * - attributeNamePrefix: '@_' (prefix all attribute names with @_)
45
+ * - stopNodes: ["*.pre", "*.p"]
46
+ * - isArray(): true or false (based on xmlParserOption)
47
+ *
48
+ * NOTE: The isArray can specify what tags to always convert into an array, we
49
+ * do not specify specific fields as it could break parsing if future
50
+ * fields are added, we parse all fields as an array.
29
51
  *
30
52
  * For more details on the parser options, see the documentation for the v4 or v5 version of the library:
31
53
  * {@link https://github.com/NaturalIntelligence/fast-xml-parser/tree/master/docs/v4}
32
54
  */
33
- function convertEncodedXmlIntoJson(encodedXml) {
34
- const options = {
55
+ /**
56
+ * Converts an encoded XML string into a JSON object using specified parsing options.
57
+ *
58
+ * @param encodedXml - The encoded XML string to be converted.
59
+ * @param xmlParserOption - The parsing option to be used. Defaults to 'withArrayOption'.
60
+ * Possible values are:
61
+ * - 'withArrayOption': Parses XML with array option enabled.
62
+ * - 'withArrayNoEntitiesOption': Parses XML with array option enabled and processes entities.
63
+ * - Any other value: Parses XML without array option.
64
+ * @returns The JSON object resulting from the XML parsing.
65
+ */
66
+ function convertEncodedXmlIntoJson(encodedXml, xmlParserOption = 'withArrayOption') {
67
+ const withArrayOption = {
68
+ processEntities: false,
35
69
  ignoreAttributes: false,
36
70
  removeNSPrefix: true,
37
71
  attributeNamePrefix: '@_',
38
- stopNodes: ['div', 'p'],
39
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
40
- isArray: (_name, _jpath, _isLeafNode, _isAttribute) => true,
72
+ stopNodes: ['*.div', '*.p'],
73
+ isArray: () => true,
41
74
  };
42
- const parser = new fast_xml_parser_1.XMLParser(options);
75
+ const withArrayNoEntitiesOption = {
76
+ processEntities: true,
77
+ ignoreAttributes: false,
78
+ removeNSPrefix: true,
79
+ attributeNamePrefix: '@_',
80
+ stopNodes: ['*.div', '*.p'],
81
+ isArray: () => true,
82
+ };
83
+ const noArrayOption = {
84
+ processEntities: false,
85
+ ignoreAttributes: false,
86
+ removeNSPrefix: true,
87
+ attributeNamePrefix: '@_',
88
+ stopNodes: ['*.div', '*.p'],
89
+ isArray: () => false,
90
+ };
91
+ const parser = new fast_xml_parser_1.XMLParser(xmlParserOption === 'withArrayOption'
92
+ ? withArrayOption
93
+ : xmlParserOption === 'withArrayNoEntitiesOption'
94
+ ? withArrayNoEntitiesOption
95
+ : noArrayOption);
43
96
  return parser.parse(encodedXml);
44
97
  }
45
98
  /**
@@ -61,11 +114,18 @@ function convertJsonIntoXML(data) {
61
114
  * @returns The decoded string with XML special characters removed.
62
115
  */
63
116
  function removeXMLSpecialCharacters(str) {
64
- //console.log('Remove special characters: ', JSON.stringify(str, null, 2));
65
117
  const result = he_1.default.decode(str);
66
- //console.log('Result of he.decode: ', JSON.stringify(result));
67
118
  return result;
68
119
  }
120
+ /**
121
+ * Removes HTML tags from the given input string.
122
+ *
123
+ * @param input - The string from which HTML tags should be removed.
124
+ * @returns A new string with all HTML tags removed.
125
+ */
126
+ function removeHtmlTags(input) {
127
+ return input.replace(/<\/?[^>]+(>|$)/g, '');
128
+ }
69
129
  /**
70
130
  * Converts a severity string to a numerical impact value.
71
131
  *
@@ -166,7 +226,7 @@ function convertEncodedHTMLIntoJson(encodedHTML) {
166
226
  });
167
227
  htmlParser.write(patchedHTML);
168
228
  htmlParser.end();
169
- const converted = convertEncodedXmlIntoJson(xmlChunks.join(''));
229
+ const converted = convertEncodedXmlIntoJson(xmlChunks.join(''), 'noArrayOption');
170
230
  let cleaned = {};
171
231
  // Some STIGs have xml tags inside of the actual text which breaks processing,
172
232
  // e.g U_ASD_STIG_V5R1_Manual-xccdf.xml and all Oracle Database STIGs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/inspec-objects",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Typescript objects for normalizing between InSpec profiles and XCCDF benchmarks",
5
5
  "main": "lib/index.js",
6
6
  "publishConfig": {