@contrast/rewriter 1.3.0 → 1.4.0
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/index.js +66 -211
- package/package.json +5 -7
package/lib/index.js
CHANGED
|
@@ -15,234 +15,89 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const { transformSync } = require('@swc/core');
|
|
18
19
|
const Module = require('module');
|
|
19
|
-
const { default: traverse } = require('@babel/traverse');
|
|
20
|
-
const parser = require('@babel/parser');
|
|
21
|
-
const { default: generate } = require('@babel/generator');
|
|
22
|
-
const t = require('@babel/types');
|
|
23
|
-
const { expression, statement } = require('@babel/template');
|
|
24
20
|
|
|
21
|
+
const rewriterPath = require.resolve('@contrast/agent-swc-plugin');
|
|
22
|
+
const unwriterPath = require.resolve('@contrast/agent-swc-plugin-unwrite');
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
module.exports = function(core) {
|
|
30
|
-
const rewriter = new Rewriter(core);
|
|
31
|
-
return core.rewriter = rewriter;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Babel will add a trailing semicolon in some cases. This will remove it if it
|
|
37
|
-
* wasn't there to begin with.
|
|
38
|
-
* @param {string} rewritten rewritten content
|
|
39
|
-
* @param {string} orig original content before rewriting
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
function removeAddedSemicolons(orig, rewritten) {
|
|
43
|
-
if (
|
|
44
|
-
rewritten.charCodeAt(rewritten.length - 1) == 59 &&
|
|
45
|
-
orig.charCodeAt(orig.length - 1) != 59
|
|
46
|
-
) {
|
|
47
|
-
rewritten = rewritten.substr(0, rewritten.length - 1);
|
|
48
|
-
}
|
|
49
|
-
return rewritten;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class Rewriter {
|
|
53
|
-
constructor(deps) {
|
|
54
|
-
const self = this;
|
|
55
|
-
this.logger = deps.logger;
|
|
56
|
-
this.visitors = [];
|
|
57
|
-
this.installedModes = [];
|
|
58
|
-
this.tokens = [];
|
|
59
|
-
this.injections = [];
|
|
60
|
-
this.methodLookups = {};
|
|
61
|
-
this.rewriteTransforms = {
|
|
62
|
-
enter(...args) {
|
|
63
|
-
for (const v of self.visitors) {
|
|
64
|
-
v(...args);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
Program: function Program(path, state) {
|
|
68
|
-
if (state.wrap) {
|
|
69
|
-
let [prefix, suffix] = Module.wrapper;
|
|
70
|
-
prefix = prefix.trim();
|
|
71
|
-
suffix = suffix.trim().replace(/;$/, '.apply(this, arguments);');
|
|
72
|
-
|
|
73
|
-
path.node.body = [
|
|
74
|
-
statement(`${prefix} %%body%% ${suffix}`)({
|
|
75
|
-
body: path.node.body
|
|
76
|
-
})
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (state.inject) {
|
|
81
|
-
path.unshiftContainer('body', self.injections);
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
CallExpression(path) {
|
|
85
|
-
if (path.node.callee.name === 'eval') {
|
|
86
|
-
path.node.arguments = [
|
|
87
|
-
t.callExpression(expression('global.ContrastMethods.eval')(), path.node.arguments)
|
|
88
|
-
];
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
BinaryExpression: function BinaryExpression(path) {
|
|
92
|
-
const method = self.methodLookups[path.node.operator];
|
|
93
|
-
if (method) {
|
|
94
|
-
path.replaceWith(
|
|
95
|
-
t.callExpression(
|
|
96
|
-
expression('ContrastMethods.%%method%%')({ method }), [
|
|
97
|
-
path.node.left,
|
|
98
|
-
path.node.right
|
|
99
|
-
]
|
|
100
|
-
)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
this.unwriteTransforms = {
|
|
106
|
-
CallExpression(path) {
|
|
107
|
-
const obj = path.node.callee.object;
|
|
108
|
-
if (obj && obj.property && obj.property.name === 'ContrastMethods') {
|
|
109
|
-
path.replaceWith(path.node.arguments[0]);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
this.install = function(mode) {
|
|
114
|
-
self.installedModes.push(mode);
|
|
115
|
-
!self.methodLookups.eval && (self.methodLookups = {
|
|
116
|
-
eval: 'eval'
|
|
117
|
-
});
|
|
24
|
+
const prefix = Module.wrapper[0];
|
|
25
|
+
const suffix = Module.wrapper[1].replace(/;$/, '.apply(this, arguments);');
|
|
118
26
|
|
|
119
|
-
|
|
120
|
-
statement(
|
|
121
|
-
'const %%id%% = global.%%id%% || (() => { throw new SyntaxError(%%errMessage%%); })();'
|
|
122
|
-
)({
|
|
123
|
-
id: 'ContrastMethods',
|
|
124
|
-
errMessage: t.stringLiteral(
|
|
125
|
-
'ContrastMethods undefined during compilation'
|
|
126
|
-
)
|
|
127
|
-
}),
|
|
128
|
-
statement(
|
|
129
|
-
'var %%name%% = global.ContrastMethods.%%id%% || %%name%%;'
|
|
130
|
-
)({ name: 'Function', id: 'Function' }),
|
|
131
|
-
);
|
|
27
|
+
/** @typedef {'assess' | 'protect'} Mode */
|
|
132
28
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// in Assess (at least for now)
|
|
137
|
-
}
|
|
138
|
-
if (self.installedModes.includes('assess')) {
|
|
139
|
-
Object.assign(self.methodLookups, {
|
|
140
|
-
'+': 'plus',
|
|
141
|
-
'===': 'tripleEqual',
|
|
142
|
-
'!==': 'notTripleEqual',
|
|
143
|
-
'==': 'doubleEqual',
|
|
144
|
-
'!=': 'notDoubleEqual'
|
|
145
|
-
});
|
|
146
|
-
self.injections.push(
|
|
147
|
-
statement(
|
|
148
|
-
'const %%name%% = global.%%id%% || %%name%%;'
|
|
149
|
-
)({ name: 'JSON', id: 'ContrastJSON' }),
|
|
150
|
-
statement(
|
|
151
|
-
'const %%name%% = global.%%id%% || %%name%%;'
|
|
152
|
-
)({ name: 'Object', id: 'ContrastObject' }),
|
|
153
|
-
statement(
|
|
154
|
-
'const %%name%% = global.%%id%% || %%name%%;'
|
|
155
|
-
)({ name: 'Number', id: 'ContrastNumber' })
|
|
156
|
-
);
|
|
157
|
-
Object.assign(self.rewriteTransforms, {
|
|
158
|
-
AssignmentExpression(path) {
|
|
159
|
-
if (path.node.operator !== '+=') return;
|
|
160
|
-
path.replaceWith(
|
|
161
|
-
t.assignmentExpression(
|
|
162
|
-
'=',
|
|
163
|
-
path.node.left,
|
|
164
|
-
t.callExpression(expression('global.ContrastMethods.plus')(), [path.node.left, path.node.right])
|
|
165
|
-
)
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
29
|
+
const rewriter = {
|
|
30
|
+
/** @type {Set<Mode>} */
|
|
31
|
+
modes: new Set(),
|
|
170
32
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Sets the rewriter to 'assess' or 'protect' mode, enabling different
|
|
35
|
+
* transforms.
|
|
36
|
+
* @param {Mode} mode
|
|
37
|
+
*/
|
|
38
|
+
install(mode) {
|
|
39
|
+
this.modes.add(mode);
|
|
40
|
+
},
|
|
174
41
|
|
|
175
42
|
/**
|
|
176
43
|
* @param {string} content the source code
|
|
177
44
|
* @param {object} opts
|
|
178
45
|
* @param {string} opts.filename e.g. 'index.js'
|
|
179
|
-
* @param {boolean} opts.
|
|
180
|
-
* @param {
|
|
181
|
-
* @param {boolean} opts.wrap
|
|
182
|
-
* @returns {
|
|
46
|
+
* @param {boolean} opts.isModule if true, file is parsed as an ES module instead of a CJS script
|
|
47
|
+
* @param {boolean} opts.inject if true, injects ContrastMethods on the global object
|
|
48
|
+
* @param {boolean} opts.wrap if true, wraps the content with a modified module wrapper IIFE
|
|
49
|
+
* @returns {import("@swc/core").Output}
|
|
183
50
|
*/
|
|
184
|
-
rewrite(content, opts = {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
sourceType: 'script',
|
|
188
|
-
}) {
|
|
189
|
-
opts.filename = opts.filename || 'no filename';
|
|
190
|
-
opts.sourceType = opts.sourceType || 'script';
|
|
191
|
-
|
|
192
|
-
const state = {
|
|
193
|
-
orig: String(content),
|
|
194
|
-
deps: [],
|
|
195
|
-
filename: opts.filename,
|
|
196
|
-
...opts
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
if (this.tokens.every((token) => state.orig.indexOf(token) === -1)) {
|
|
200
|
-
return { code: state.orig };
|
|
51
|
+
rewrite(content, opts = {}) {
|
|
52
|
+
if (opts.wrap) {
|
|
53
|
+
content = `${prefix}${content}${suffix}`;
|
|
201
54
|
}
|
|
202
55
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
'
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const result = generate(
|
|
220
|
-
ast,
|
|
221
|
-
{
|
|
222
|
-
jsonCompatibleStrings: true,
|
|
223
|
-
sourceMaps: true,
|
|
224
|
-
sourceFileName: state.filename
|
|
56
|
+
return transformSync(content, {
|
|
57
|
+
filename: opts.filename,
|
|
58
|
+
isModule: opts.isModule,
|
|
59
|
+
jsc: {
|
|
60
|
+
target: 'es2019', // should work for node >14
|
|
61
|
+
experimental: {
|
|
62
|
+
plugins: [
|
|
63
|
+
[
|
|
64
|
+
rewriterPath,
|
|
65
|
+
{
|
|
66
|
+
assess: this.modes.has('assess'),
|
|
67
|
+
inject: this.modes.has('assess') && opts.inject,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
],
|
|
71
|
+
},
|
|
225
72
|
},
|
|
226
|
-
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
result.code = removeAddedSemicolons(content, result.code);
|
|
230
|
-
result.deps = state.deps;
|
|
231
|
-
|
|
232
|
-
return result;
|
|
233
|
-
}
|
|
73
|
+
sourceMaps: true,
|
|
74
|
+
});
|
|
75
|
+
},
|
|
234
76
|
|
|
235
77
|
/**
|
|
236
|
-
* @param {string}
|
|
237
|
-
* @param {object} opts
|
|
78
|
+
* @param {string} content
|
|
238
79
|
* @returns {string}
|
|
239
80
|
*/
|
|
240
|
-
unwrite(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
81
|
+
unwrite(content) {
|
|
82
|
+
return transformSync(content, {
|
|
83
|
+
jsc: {
|
|
84
|
+
target: 'es2019', // should work for node >14
|
|
85
|
+
experimental: {
|
|
86
|
+
plugins: [[unwriterPath, {}]],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}).code;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
247
92
|
|
|
248
|
-
|
|
93
|
+
/** @typedef {{}} Core */
|
|
94
|
+
/** @typedef {typeof rewriter} Rewriter */
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Core} core
|
|
98
|
+
* @returns {Rewriter}
|
|
99
|
+
*/
|
|
100
|
+
module.exports = function init(core) {
|
|
101
|
+
core.rewriter = rewriter;
|
|
102
|
+
return rewriter;
|
|
103
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/rewriter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "A transpilation tool mainly used for instrumentation",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -17,12 +17,10 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@
|
|
21
|
-
"@
|
|
22
|
-
"@babel/template": "^7.16.7",
|
|
23
|
-
"@babel/traverse": "^7.17.9",
|
|
24
|
-
"@babel/types": "^7.16.7",
|
|
20
|
+
"@contrast/agent-swc-plugin": "^1.1.0",
|
|
21
|
+
"@contrast/agent-swc-plugin-unwrite": "^1.1.0",
|
|
25
22
|
"@contrast/synchronous-source-maps": "^1.1.3",
|
|
23
|
+
"@swc/core": "1.3.39",
|
|
26
24
|
"multi-stage-sourcemap": "^0.3.1"
|
|
27
25
|
}
|
|
28
|
-
}
|
|
26
|
+
}
|