@mitre/inspec-objects 0.0.32 → 0.0.34
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/lib/utilities/update.js +155 -45
- package/package.json +1 -1
package/lib/utilities/update.js
CHANGED
|
@@ -31,63 +31,173 @@ function projectValuesOntoExistingObj(dst, src, currentPath = '') {
|
|
|
31
31
|
}
|
|
32
32
|
return dst;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
function getRangesForLines(text) {
|
|
35
|
+
/*
|
|
36
|
+
Returns an array containing two numerical indices (i.e., start and stop
|
|
37
|
+
line numbers) for each string or multi-line comment, given raw text as
|
|
38
|
+
an input parameter. The raw text is a string containing the entirety of an
|
|
39
|
+
InSpec control.
|
|
40
|
+
|
|
41
|
+
Algorithm utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep
|
|
42
|
+
track of string delimiters and their associated line numbers, respectively.
|
|
43
|
+
|
|
44
|
+
Combinations Handled:
|
|
45
|
+
- Single quotes (')
|
|
46
|
+
- Double quotes (")
|
|
47
|
+
- Back ticks (`)
|
|
48
|
+
- Mixed quotes ("`'")
|
|
49
|
+
- Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {},
|
|
50
|
+
[], <>, most non-alphanumeric characters); (e.g., "%q()")
|
|
51
|
+
- Percent literals (%; delimiters: (), {}, [], <>, most non-
|
|
52
|
+
alphanumeric characters); (e.g., "%()")
|
|
53
|
+
- Multi-line comments (e.g., =begin\nSome comment\n=end)
|
|
54
|
+
- Variable delimiters (i.e., paranthesis: (); array: []; hash: {})
|
|
55
|
+
*/
|
|
56
|
+
const stringDelimiters = { '(': ')', '{': '}', '[': ']', '<': '>' };
|
|
57
|
+
const variableDelimiters = { '(': ')', '{': '}', '[': ']' };
|
|
58
|
+
const quotes = '\'"`';
|
|
59
|
+
const strings = 'qQriIwWxs';
|
|
60
|
+
let skipCharLength;
|
|
61
|
+
(function (skipCharLength) {
|
|
62
|
+
skipCharLength[skipCharLength["string"] = '('.length] = "string";
|
|
63
|
+
skipCharLength[skipCharLength["percentString"] = 'q('.length] = "percentString";
|
|
64
|
+
skipCharLength[skipCharLength["commentBegin"] = '=begin'.length] = "commentBegin";
|
|
65
|
+
})(skipCharLength || (skipCharLength = {}));
|
|
66
|
+
const stack = [];
|
|
67
|
+
const rangeStack = [];
|
|
68
|
+
const ranges = [];
|
|
69
|
+
const lines = text.split('\n');
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
let j = 0;
|
|
72
|
+
while (j < lines[i].length) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
let char = line[j];
|
|
75
|
+
const isEmptyStack = (stack.length == 0);
|
|
76
|
+
const isNotEmptyStack = (stack.length > 0);
|
|
77
|
+
const isQuoteChar = quotes.includes(char);
|
|
78
|
+
const isNotEscapeChar = ((j == 0) || (j > 0 && line[j - 1] != '\\'));
|
|
79
|
+
const isPercentChar = (char == '%');
|
|
80
|
+
const isVariableDelimiterChar = Object.keys(variableDelimiters).includes(char);
|
|
81
|
+
const isStringDelimiterChar = ((j < line.length - 1) && (/^[^A-Za-z0-9]$/.test(line[j + 1])));
|
|
82
|
+
const isCommentBeginChar = ((j == 0) && (line.length >= 6) && (line.slice(0, 6) == '=begin'));
|
|
83
|
+
const isPercentStringKeyChar = ((j < line.length - 1) && (strings.includes(line[j + 1])));
|
|
84
|
+
const isPercentStringDelimiterChar = ((j < line.length - 2) && (/^[^A-Za-z0-9]$/.test(line[j + 2])));
|
|
85
|
+
const isPercentString = (isPercentStringKeyChar && isPercentStringDelimiterChar);
|
|
86
|
+
let baseCondition = (isEmptyStack && isNotEscapeChar);
|
|
87
|
+
const quotePushCondition = (baseCondition && isQuoteChar);
|
|
88
|
+
const variablePushCondition = (baseCondition && isVariableDelimiterChar);
|
|
89
|
+
const stringPushCondition = (baseCondition && isPercentChar && isStringDelimiterChar);
|
|
90
|
+
const percentStringPushCondition = (baseCondition && isPercentChar && isPercentString);
|
|
91
|
+
const commentBeginCondition = (baseCondition && isCommentBeginChar);
|
|
92
|
+
if (stringPushCondition) {
|
|
93
|
+
j += skipCharLength.string; // j += 1
|
|
94
|
+
}
|
|
95
|
+
else if (percentStringPushCondition) {
|
|
96
|
+
j += skipCharLength.percentString; // j += 2
|
|
97
|
+
}
|
|
98
|
+
else if (commentBeginCondition) {
|
|
99
|
+
j += skipCharLength.commentBegin; // j += 6
|
|
100
|
+
}
|
|
101
|
+
char = line[j];
|
|
102
|
+
baseCondition = (isNotEmptyStack && isNotEscapeChar);
|
|
103
|
+
const delimiterCondition = (baseCondition && Object.keys(stringDelimiters).includes(stack[stack.length - 1]));
|
|
104
|
+
const delimiterPushCondition = (delimiterCondition && (stack[stack.length - 1] == char));
|
|
105
|
+
const delimiterPopCondition = (delimiterCondition && (stringDelimiters[stack[stack.length - 1]] == char));
|
|
106
|
+
const basePopCondition = (baseCondition && (stack[stack.length - 1] == char) && !Object.keys(stringDelimiters).includes(char));
|
|
107
|
+
const isCommentEndChar = ((j == 0) && (line.length >= 4) && (line.slice(0, 4) == '=end'));
|
|
108
|
+
const commentEndCondition = (baseCondition && isCommentEndChar && (stack[stack.length - 1] == '=begin'));
|
|
109
|
+
const popCondition = (basePopCondition || delimiterPopCondition || commentEndCondition);
|
|
110
|
+
const pushCondition = (quotePushCondition || variablePushCondition || stringPushCondition ||
|
|
111
|
+
percentStringPushCondition || delimiterPushCondition || commentBeginCondition);
|
|
112
|
+
if (popCondition) {
|
|
113
|
+
stack.pop();
|
|
114
|
+
rangeStack[rangeStack.length - 1].push(i);
|
|
115
|
+
const range_ = rangeStack.pop();
|
|
116
|
+
if (rangeStack.length == 0) {
|
|
117
|
+
ranges.push(range_);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (pushCondition) {
|
|
121
|
+
if (commentBeginCondition) {
|
|
122
|
+
stack.push('=begin');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
stack.push(char);
|
|
126
|
+
}
|
|
127
|
+
rangeStack.push([i]);
|
|
128
|
+
}
|
|
129
|
+
j++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return ranges;
|
|
133
|
+
}
|
|
134
|
+
function joinMultiLineStringsFromRanges(text, ranges) {
|
|
135
|
+
/*
|
|
136
|
+
Returns an array of strings and joined strings at specified ranges, given
|
|
137
|
+
raw text as an input parameter.
|
|
138
|
+
*/
|
|
139
|
+
const originalLines = text.split('\n');
|
|
140
|
+
const joinedLines = [];
|
|
141
|
+
let i = 0;
|
|
142
|
+
while (i < originalLines.length) {
|
|
143
|
+
let foundInRanges = false;
|
|
144
|
+
for (const [startIndex, stopIndex] of ranges) {
|
|
145
|
+
if (i >= startIndex && i <= stopIndex) {
|
|
146
|
+
joinedLines.push(originalLines.slice(startIndex, stopIndex + 1).join('\n'));
|
|
147
|
+
foundInRanges = true;
|
|
148
|
+
i = stopIndex;
|
|
46
149
|
break;
|
|
150
|
+
}
|
|
47
151
|
}
|
|
152
|
+
if (!foundInRanges) {
|
|
153
|
+
joinedLines.push(originalLines[i]);
|
|
154
|
+
}
|
|
155
|
+
i++;
|
|
48
156
|
}
|
|
49
|
-
return
|
|
157
|
+
return joinedLines;
|
|
158
|
+
}
|
|
159
|
+
function getMultiLineRanges(ranges) {
|
|
160
|
+
/*
|
|
161
|
+
Drops ranges with the same start and stop line numbers (i.e., strings
|
|
162
|
+
that populate a single line)
|
|
163
|
+
*/
|
|
164
|
+
const multiLineRanges = [];
|
|
165
|
+
for (const [start, stop] of ranges) {
|
|
166
|
+
if (start !== stop) {
|
|
167
|
+
multiLineRanges.push([start, stop]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return multiLineRanges;
|
|
50
171
|
}
|
|
51
172
|
/*
|
|
52
173
|
This is the most likely thing to break if you are getting code formatting issues.
|
|
53
174
|
Extract the existing describe blocks (what is actually run by inspec for validation)
|
|
54
175
|
*/
|
|
55
176
|
function getExistingDescribeFromControl(control) {
|
|
56
|
-
// Algorithm:
|
|
57
|
-
// Locate the start and end of the control string
|
|
58
|
-
// Update the end of the control that contains information (if empty lines are at the end of the control)
|
|
59
|
-
// loop: until the start index is changed (loop is done from the bottom up)
|
|
60
|
-
// Clean testing array entry line (removes any non-print characters)
|
|
61
|
-
// if: line starts with meta-information 'tag' or 'ref'
|
|
62
|
-
// set start index to found location
|
|
63
|
-
// break out of the loop
|
|
64
|
-
// end
|
|
65
|
-
// end
|
|
66
|
-
// Remove any empty lines after the start index (in any)
|
|
67
|
-
// Extract the describe block from the audit control given the start and end indices
|
|
68
|
-
// Assumptions:
|
|
69
|
-
// 1 - The meta-information 'tag' or 'ref' precedes the describe block
|
|
70
|
-
// Pros: Solves the potential issue with option 1, as the lookup for the meta-information
|
|
71
|
-
// 'tag' or 'ref' is expected to the at the beginning of the line.
|
|
72
177
|
if (control.code) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
178
|
+
// Join multi-line strings in InSpec control.
|
|
179
|
+
const ranges = getRangesForLines(control.code);
|
|
180
|
+
const multiLineRanges = getMultiLineRanges(ranges);
|
|
181
|
+
const lines = joinMultiLineStringsFromRanges(control.code, multiLineRanges); // Array of lines representing the full InSpec control, with multi-line strings collapsed
|
|
182
|
+
// Define RegExp for lines to skip when searching for describe block.
|
|
183
|
+
const skip = ['control\\W', ' title\\W', ' desc\\W', ' impact\\W', ' tag\\W', ' ref\\W'];
|
|
184
|
+
const skipRegExp = RegExp(skip.map(x => `(^${x})`).join('|'));
|
|
185
|
+
// Extract describe block from InSpec control with collapsed multiline strings.
|
|
186
|
+
const describeBlock = [];
|
|
187
|
+
let ignoreNewLine = true;
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
const checkRegExp = ((line.trim() !== '') && !skipRegExp.test(line));
|
|
190
|
+
const checkNewLine = ((line.trim() === '') && !ignoreNewLine);
|
|
191
|
+
// Include '\n' if it is part of describe block, otherwise skip line.
|
|
192
|
+
if (checkRegExp || checkNewLine) {
|
|
193
|
+
describeBlock.push(line);
|
|
194
|
+
ignoreNewLine = false;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
ignoreNewLine = true;
|
|
85
198
|
}
|
|
86
|
-
index--;
|
|
87
199
|
}
|
|
88
|
-
|
|
89
|
-
existingDescribeBlock = auditControl.slice(indexStart, indexEnd + 1).join('\n').toString();
|
|
90
|
-
return existingDescribeBlock;
|
|
200
|
+
return describeBlock.slice(0, describeBlock.length - 2).join('\n'); // Drop trailing ['end', '\n'] from Control block.
|
|
91
201
|
}
|
|
92
202
|
else {
|
|
93
203
|
return '';
|