@contrast/rewriter 1.3.1 → 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 -200
- package/package.json +5 -7
package/lib/index.js
CHANGED
|
@@ -15,223 +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
|
-
Object.assign(self.rewriteTransforms, {
|
|
147
|
-
AssignmentExpression(path) {
|
|
148
|
-
if (path.node.operator !== '+=') return;
|
|
149
|
-
path.replaceWith(
|
|
150
|
-
t.assignmentExpression(
|
|
151
|
-
'=',
|
|
152
|
-
path.node.left,
|
|
153
|
-
t.callExpression(expression('global.ContrastMethods.plus')(), [path.node.left, path.node.right])
|
|
154
|
-
)
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
29
|
+
const rewriter = {
|
|
30
|
+
/** @type {Set<Mode>} */
|
|
31
|
+
modes: new Set(),
|
|
159
32
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
},
|
|
163
41
|
|
|
164
42
|
/**
|
|
165
43
|
* @param {string} content the source code
|
|
166
44
|
* @param {object} opts
|
|
167
45
|
* @param {string} opts.filename e.g. 'index.js'
|
|
168
|
-
* @param {boolean} opts.
|
|
169
|
-
* @param {
|
|
170
|
-
* @param {boolean} opts.wrap
|
|
171
|
-
* @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}
|
|
172
50
|
*/
|
|
173
|
-
rewrite(content, opts = {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
sourceType: 'script',
|
|
177
|
-
}) {
|
|
178
|
-
opts.filename = opts.filename || 'no filename';
|
|
179
|
-
opts.sourceType = opts.sourceType || 'script';
|
|
180
|
-
|
|
181
|
-
const state = {
|
|
182
|
-
orig: String(content),
|
|
183
|
-
deps: [],
|
|
184
|
-
filename: opts.filename,
|
|
185
|
-
...opts
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
if (this.tokens.every((token) => state.orig.indexOf(token) === -1)) {
|
|
189
|
-
return { code: state.orig };
|
|
51
|
+
rewrite(content, opts = {}) {
|
|
52
|
+
if (opts.wrap) {
|
|
53
|
+
content = `${prefix}${content}${suffix}`;
|
|
190
54
|
}
|
|
191
55
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
'
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const result = generate(
|
|
209
|
-
ast,
|
|
210
|
-
{
|
|
211
|
-
jsonCompatibleStrings: true,
|
|
212
|
-
sourceMaps: true,
|
|
213
|
-
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
|
+
},
|
|
214
72
|
},
|
|
215
|
-
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
result.code = removeAddedSemicolons(content, result.code);
|
|
219
|
-
result.deps = state.deps;
|
|
220
|
-
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
73
|
+
sourceMaps: true,
|
|
74
|
+
});
|
|
75
|
+
},
|
|
223
76
|
|
|
224
77
|
/**
|
|
225
|
-
* @param {string}
|
|
226
|
-
* @param {object} opts
|
|
78
|
+
* @param {string} content
|
|
227
79
|
* @returns {string}
|
|
228
80
|
*/
|
|
229
|
-
unwrite(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
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
|
+
};
|
|
236
92
|
|
|
237
|
-
|
|
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
|
+
}
|