log-internal.js
5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import fs from './fs';
import _ from 'lodash';
const DEFAULT_REPLACER = '**SECURE**';
/**
* @typedef {Object} SecureValuePreprocessingRule
* @property {RegExp} pattern The parsed pattern which is going to be used for replacement
* @property {string} replacer [DEFAULT_SECURE_REPLACER] The replacer value to use. By default
* equals to `DEFAULT_SECURE_REPLACER`
*/
class SecureValuesPreprocessor {
constructor () {
this._rules = [];
}
/**
* @returns {Array<SecureValuePreprocessingRule>} The list of successfully
* parsed preprocessing rules
*/
get rules () {
return this._rules;
}
/**
* @typedef {Object} Rule
* @property {string} pattern A valid RegExp pattern to be replaced
* @property {string} text A text match to replace. Either this property or the
* above one must be provided. `pattern` has priority over `text` if both are provided.
* @property {string} flags ['g'] Regular expression flags for the given pattern.
* Supported flag are the same as for the standard JavaScript RegExp constructor:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2
* The 'g' (global matching) is always enabled though.
* @property {string} replacer [DEFAULT_SECURE_REPLACER] The replacer value to use. By default
* equals to `DEFAULT_SECURE_REPLACER`
*/
/**
* Parses single rule from the given JSON file
*
* @param {string|Rule} rule The rule might either be represented as a single string
* or a configuration object
* @throws {Error} If there was an error while parsing the rule
* @returns {SecureValuePreprocessingRule} The parsed rule
*/
parseRule (rule) {
const raiseError = (msg) => {
throw new Error(`${JSON.stringify(rule)} -> ${msg}`);
};
let pattern;
let replacer = DEFAULT_REPLACER;
let flags = ['g'];
if (_.isString(rule)) {
if (rule.length === 0) {
raiseError('The value must not be empty');
}
pattern = `\\b${_.escapeRegExp(rule)}\\b`;
} else if (_.isPlainObject(rule)) {
if (_.has(rule, 'pattern')) {
if (!_.isString(rule.pattern) || rule.pattern.length === 0) {
raiseError(`The value of 'pattern' must be a valid non-empty string`);
}
pattern = rule.pattern;
} else if (_.has(rule, 'text')) {
if (!_.isString(rule.text) || rule.text.length === 0) {
raiseError(`The value of 'text' must be a valid non-empty string`);
}
pattern = `\\b${_.escapeRegExp(rule.text)}\\b`;
}
if (!pattern) {
raiseError(`Must either have a field named 'pattern' or 'text'`);
}
if (_.has(rule, 'flags')) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2
for (const flag of ['i', 'g', 'm', 's', 'u', 'y']) {
if (_.includes(rule.flags, flag)) {
flags.push(flag);
}
}
flags = _.uniq(flags);
}
if (_.isString(rule.replacer)) {
replacer = rule.replacer;
}
} else {
raiseError('Must either be a string or an object');
}
try {
return {
pattern: new RegExp(pattern, flags.join('')),
replacer,
};
} catch (e) {
raiseError(e.message);
}
}
/**
* Loads rules from the given JSON file
*
* @param {string|Array<string|Rule>} source The full path to the JSON file containing secure
* values replacement rules or the rules themselves represented as an array
* @throws {Error} If the format of the source file is invalid or
* it does not exist
* @returns {Array<string>} The list of issues found while parsing each rule.
* An empty list is returned if no rule parsing issues were found
*/
async loadRules (source) {
let rules;
if (_.isArray(source)) {
rules = source;
} else {
if (!await fs.exists(source)) {
throw new Error(`'${source}' does not exist or is not accessible`);
}
try {
rules = JSON.parse(await fs.readFile(source, 'utf8'));
} catch (e) {
throw new Error(`'${source}' must be a valid JSON file. Original error: ${e.message}`);
}
if (!_.isArray(rules)) {
throw new Error(`'${source}' must contain a valid JSON array`);
}
}
const issues = [];
this._rules = [];
for (const rule of rules) {
try {
this._rules.push(this.parseRule(rule));
} catch (e) {
issues.push(e.message);
}
}
return issues;
}
/**
* Performs secure values replacement inside the given string
* according to the previously loaded rules. No replacement is made
* if there are no rules or the given value is not a string
*
* @param {string} str The string to make replacements in
* @returns {string} The string with replacements made
*/
preprocess (str) {
if (this._rules.length === 0 || !_.isString(str)) {
return str;
}
let result = str;
for (const rule of this._rules) {
result = result.replace(rule.pattern, rule.replacer);
}
return result;
}
}
const SECURE_VALUES_PREPROCESSOR = new SecureValuesPreprocessor();
export { SECURE_VALUES_PREPROCESSOR, SecureValuesPreprocessor };
export default SECURE_VALUES_PREPROCESSOR;