@dacely/toildefender 0.1.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.
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+
3
+ const METHODS_INJECT = `
4
+ function veilmark$mergeArguments(a, b) {
5
+ return Array.prototype.slice.call(a).concat(Array.prototype.slice.call(b));
6
+ }
7
+
8
+ function veilmark$bind() {
9
+ var fn = arguments[0], prepend = Array.prototype.slice.call(arguments, 1);
10
+ var wrapper = function() {
11
+ return fn.apply(this, prepend.concat(Array.prototype.slice.call(arguments)));
12
+ };
13
+ wrapper.prototype = fn.prototype;
14
+ return wrapper;
15
+ }
16
+
17
+ function veilmark$sliceArguments(args, num) {
18
+ return Array.prototype.slice.call(args, num);
19
+ }
20
+
21
+ function veilmark$toObject(schema, values) {
22
+ var obj = {};
23
+ if (values === undefined) {
24
+ for (var legacy = 0; legacy < schema.length; legacy += 2) {
25
+ obj[schema[legacy]] = schema[legacy + 1];
26
+ }
27
+ return obj;
28
+ }
29
+ var cursor = 2;
30
+ var salt = schema[0];
31
+ var count = schema[1];
32
+ for (var i = 0; i < count; i += 1) {
33
+ var len = schema[cursor++] ^ ((salt + i * 131) & 65535);
34
+ var key = "";
35
+ for (var j = 0; j < len; j += 1) {
36
+ key += String.fromCharCode(schema[cursor++] ^ ((salt + i * 257 + j * 17) & 65535));
37
+ }
38
+ obj[key] = values[i];
39
+ }
40
+ return obj;
41
+ }
42
+
43
+ function veilmark$decodeString(arr) {
44
+ return arr.map(function(x) { return String.fromCharCode(x & ~0 >>> 16) + String.fromCharCode(x >> 16); }).join("");
45
+ }
46
+
47
+ function veilmark$fromCharCodes() {
48
+ return String.fromCharCode.apply(null, arguments);
49
+ }
50
+
51
+ `;
52
+
53
+ var assert = require("assert");
54
+ var fs = require("fs");
55
+
56
+ var _ = require("lodash");
57
+ var escope = require("escope");
58
+ var esprima = require("esprima");
59
+
60
+ var estest = require("../estest");
61
+ var traverser = require("../traverser");
62
+ var utils = require("../utils");
63
+
64
+ const ANON_METHOD_ID = "veilmark$anonymousMethodId";
65
+
66
+ /**
67
+ * Wrap function with veilmark$bind.
68
+ * @param {Identifier} Function identifier
69
+ * @returns {Node} Wrapped function
70
+ */
71
+ function createMethodStub(id) {
72
+ assert.equal(id.type, "Identifier");
73
+
74
+ return {
75
+ type: "CallExpression",
76
+ callee: { type: "Identifier", name: "veilmark$bind" },
77
+ arguments: [
78
+ id
79
+ ]
80
+ };
81
+ }
82
+
83
+ function anonymousMethodName(node) {
84
+ assert.equal(node.type, "FunctionExpression");
85
+
86
+ if (!node[ANON_METHOD_ID]) {
87
+ Object.defineProperty(node, ANON_METHOD_ID, {
88
+ configurable: false,
89
+ enumerable: false,
90
+ value: `veilmark$anon$${utils.hash(node)}`
91
+ });
92
+ }
93
+
94
+ return node[ANON_METHOD_ID];
95
+ }
96
+
97
+ /**
98
+ * Get index of argument in function.
99
+ * @param {Function} method Function
100
+ * @param {Identifier} identifier} Argument identifier
101
+ * @returns {number} Index of argument
102
+ */
103
+ function getArgumentIndex(method, identifier) {
104
+ assert.ok(estest.isFunction(method));
105
+ assert.equal(identifier.type, "Identifier");
106
+
107
+ return _.findIndex(method.params, x => x.name == identifier.name);
108
+ }
109
+
110
+ function rawArgumentsIdentifier() {
111
+ return {
112
+ type: "Identifier",
113
+ name: "arguments",
114
+ veilmark$rawArguments: true
115
+ };
116
+ }
117
+
118
+ module.exports = class Methods {
119
+
120
+ constructor (logger) {
121
+ this.logger = logger;
122
+ }
123
+
124
+ /**
125
+ * Adds helper methods to the beginning of the app.
126
+ * @param {Node} Root node
127
+ */
128
+ addCustomBind (ast) {
129
+ assert.ok(estest.isNode(ast));
130
+
131
+ var code = esprima.parse(METHODS_INJECT);
132
+ code.type = "BlockStatement";
133
+ ast.body.splice(0, 0, code);
134
+ }
135
+
136
+ /**
137
+ * Checks whether a method refers to the "arguments" array.
138
+ * @param {Function} method
139
+ * @param {ScopeManager} scopeManager
140
+ * @returns {boolean}
141
+ */
142
+ methodRefersToArguments (method, scopeManager) {
143
+ assert.ok(estest.isFunction(method));
144
+ assert.ok(scopeManager);
145
+
146
+ return scopeManager
147
+ .acquire(method)
148
+ .references
149
+ .some(reference => !utils.isResolvedReference(reference) && reference.identifier.name == "arguments");
150
+ }
151
+
152
+ /**
153
+ * Inserts code to copy/slice arguments from the arguments array like
154
+ * function () { ... }
155
+ * to
156
+ * function () { var veilmark$arguments = veilmark$sliceArguments(arguments, 1); ... }
157
+ * @param {Function} method
158
+ * @param {number} num Number of arguments to be sliced off. 0 if none.
159
+ */
160
+ removeFirstArguments (method, num) {
161
+ assert.ok(estest.isFunction(method));
162
+ assert.equal(typeof num, "number");
163
+
164
+ method.body.body.splice(0, 0, {
165
+ type: "VariableDeclaration",
166
+ kind: "var",
167
+ declarations: [
168
+ {
169
+ type: "VariableDeclarator",
170
+ id: { type: "Identifier", name: "veilmark$arguments" },
171
+ init: rawArgumentsIdentifier()
172
+ },
173
+ {
174
+ type: "VariableDeclarator",
175
+ id: { type: "Identifier", name: "veilmark$bareArguments" },
176
+ init: num > 0 ? {
177
+ type: "CallExpression",
178
+ callee: { type: "Identifier", name: "veilmark$sliceArguments" },
179
+ arguments: [
180
+ rawArgumentsIdentifier(),
181
+ { type: "Literal", value: num, veilmark$removeFirstArguments: true }
182
+ ]
183
+ } : rawArgumentsIdentifier()
184
+ }
185
+ ],
186
+ veilmark$reassigningArguments: true,
187
+ veilmark$followsSlicingArguments: num > 0
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Lists all methods.
193
+ * @param {Node} ast Root node
194
+ * @returns {string[]} Method names
195
+ */
196
+ listMethods (ast) {
197
+ assert.ok(estest.isNode(ast));
198
+
199
+ var methods = [];
200
+
201
+ traverser.traverse(ast, [], (node, stack) => {
202
+ if (node.type == "FunctionDeclaration") { // Statement
203
+ methods.push(node.id.name);
204
+ } else if (node.type == "FunctionExpression") { // Expression
205
+ methods.push(anonymousMethodName(node));
206
+ }
207
+
208
+ return node;
209
+ });
210
+
211
+ return methods;
212
+ }
213
+
214
+ /**
215
+ * Extracts all methods from the AST.
216
+ * @param {Node} ast Root node
217
+ * @returns {Function[]}
218
+ */
219
+ extractMethods (ast) {
220
+ assert.ok(estest.isNode(ast));
221
+
222
+ var methods = [];
223
+
224
+ traverser.traverse(ast, [], (node, stack) => {
225
+ if (node.type == "FunctionDeclaration") { // Statement
226
+ methods.push(node);
227
+ return { type: "ExpressionStatement", expression: createMethodStub(node.id) }; // This is not ideal
228
+ } else if (node.type == "FunctionExpression") { // Expression
229
+ var id = anonymousMethodName(node);
230
+ // Merge into old object instead of creating a new one to preserve object references
231
+ methods.push(_.assign(node, {
232
+ type: "FunctionDeclaration",
233
+ id: { type: "Identifier", name: id }
234
+ }));
235
+ return createMethodStub({ type: "Identifier", name: id });
236
+ }
237
+
238
+ return node;
239
+ });
240
+
241
+ return methods;
242
+ }
243
+
244
+ /**
245
+ * Replaces direct argument references with arguments references like
246
+ * function (a) { return a; }
247
+ * to
248
+ * function (a) { return veilmark$arguments[0]; }
249
+ * @param {Function} method Function whose body will be transformed
250
+ * @param {boolean} useReassignedVariable Use veilmark$arguments instead of arguments
251
+ * @returns {Function} Function from method parameter
252
+ */
253
+ replaceArgumentReferences (method, useReassignedVariable) {
254
+ assert.ok(estest.isFunction(method));
255
+
256
+ traverser.traverse(method.body, [], (node, stack) => {
257
+ if (node.type == "Identifier") {
258
+ var nestedFunction = stack.some(frame => estest.isFunction(frame.node));
259
+ if (useReassignedVariable && node.name == "arguments" && !node.veilmark$rawArguments && !nestedFunction) {
260
+ return { type: "Identifier", name: "veilmark$bareArguments" };
261
+ }
262
+ var index = getArgumentIndex(method, node);
263
+ if (index != -1) {
264
+ return {
265
+ type: "MemberExpression",
266
+ object: { type: "Identifier", name: useReassignedVariable ? "veilmark$arguments" : "arguments" },
267
+ property: { type: "Literal", value: index },
268
+ computed: true
269
+ };
270
+ }
271
+ }
272
+
273
+ return node;
274
+ });
275
+
276
+ method.params = [];
277
+
278
+ return method;
279
+ }
280
+
281
+ /**
282
+ * Replaces function calls with main calls like
283
+ * test()
284
+ * to
285
+ * veilmark$bind(main, 1234)()
286
+ * @param {Node} ast Root node
287
+ * @param {Object[]} methodEntryExitPoints Method entry point table
288
+ * @param {number} methodEntryExitPoints[].entry Entry point
289
+ */
290
+ replaceFunctionCalls (ast, methodEntryExitPoints) {
291
+ assert.ok(estest.isNode(ast));
292
+ assert.equal(typeof methodEntryExitPoints, "object");
293
+
294
+ traverser.traverse(ast, [], (node, stack) => {
295
+ if (node.type == "Identifier" && methodEntryExitPoints[node.name] && methodEntryExitPoints[node.name].entry) {
296
+ return {
297
+ type: "CallExpression",
298
+ callee: { type: "Identifier", name: "veilmark$bind" },
299
+ arguments: [
300
+ { type: "Identifier", name: "main" },
301
+ { type: "Identifier", name: methodEntryExitPoints[node.name].entry }
302
+ ]
303
+ };
304
+ }
305
+ return node;
306
+ });
307
+ }
308
+
309
+ /**
310
+ * Bumps all arguments indices like
311
+ * veilmark$arguments[0]
312
+ * to
313
+ * veilmark$arguments[1]
314
+ * @param {Function} method Function whose body will be transformed
315
+ * @param {number} inc Number to be added to all argument indices
316
+ */
317
+ bumpArgumentsIndices (method, inc) {
318
+ assert.ok(estest.isFunction(method));
319
+ assert.equal(typeof inc, "number");
320
+
321
+ traverser.traverse(method.body, [], (node, stack) => {
322
+ if (node.type == "MemberExpression" && node.object.type == "Identifier" && node.object.name == "veilmark$arguments") {
323
+ node.property.value += inc;
324
+ }
325
+ if (node.veilmark$removeFirstArguments) {
326
+ node.value += inc;
327
+ }
328
+ return node;
329
+ });
330
+ }
331
+
332
+ };
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+
3
+ var assert = require("assert");
4
+ var path = require("path");
5
+
6
+ var _ = require("lodash");
7
+ var escope = require("escope");
8
+
9
+ var estest = require("../estest");
10
+ var traverser = require("../traverser");
11
+ var ESUtils = require("../esutils");
12
+ var utils = require("../utils");
13
+
14
+ /**
15
+ * Transform calls to require().
16
+ * @param {Node} node Root node
17
+ * @param {Function} processor Transformer
18
+ * @returns {Node} Root node
19
+ */
20
+ function findRequires(node, processor) {
21
+ assert.ok(estest.isNode(node));
22
+ assert.equal(typeof processor, "function");
23
+
24
+ return traverser.traverse(node, [], (node, stack) => {
25
+ if (node.type == "CallExpression" && node.callee.type == "Identifier" && node.callee.name == "require") {
26
+ return processor(node, stack);
27
+ } else {
28
+ return node;
29
+ }
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Split path into parts.
35
+ * @param {string} path
36
+ * @returns {string[]}
37
+ */
38
+ function splitPath(path) {
39
+ return path.split(/[\/\\]/g).filter(x => x != null && x.length > 0);
40
+ }
41
+
42
+ /**
43
+ * Normalize path.
44
+ * @param {string[]} path
45
+ * @returns {string}
46
+ */
47
+ function normalizePath(path) {
48
+ var parts = splitPath(path);
49
+
50
+ for (var i = parts.length - 1; i >= 0; --i) {
51
+ if (parts[i] == "" || parts[i] == ".") {
52
+ parts.splice(i, 1);
53
+ } else if (parts[i] == "..") {
54
+ parts.splice(i - 1, 2);
55
+ }
56
+ }
57
+
58
+ return parts.join("/");
59
+ }
60
+
61
+ /**
62
+ * Get directory from path.
63
+ * @param {string} path
64
+ * @returns {string}
65
+ */
66
+ function getPathDir(path) {
67
+ return splitPath(path).slice(0, -1).join("/");
68
+ }
69
+
70
+ /**
71
+ * Resolve path.
72
+ * TODO: This doesnt work as expected when path starts with a slash. Fix this.
73
+ * @param {string} curr Executing script
74
+ * @param {string} path Path
75
+ * @returns {string}
76
+ */
77
+ function resolvePath(curr, path) {
78
+ return normalizePath(getPathDir(curr) + "/" + path);
79
+ }
80
+
81
+ module.exports = class Modules {
82
+
83
+ constructor (logger) {
84
+ this.logger = logger;
85
+ this.esutils = new ESUtils(logger);
86
+ }
87
+
88
+ /**
89
+ * Replace references to exports and module.exports.
90
+ * @param {Node} ast Root node
91
+ * @param {Node} replacement Replacement
92
+ * @returns {Node} Root node
93
+ */
94
+ replaceExportsReferences (ast, replacement) {
95
+ this.esutils.setParentsRecursive(ast);
96
+
97
+ var scopeManager = escope.analyze(ast, { optimistic: true });
98
+
99
+ scopeManager.scopes.forEach(scope => {
100
+ scope.references
101
+ .filter(reference => !utils.isResolvedReference(reference))
102
+ .forEach(reference => {
103
+ var parent = reference.identifier.veilmark$parent;
104
+
105
+ if (reference.identifier.name == "exports") {
106
+ this.esutils.replaceNode(ast, reference.identifier, utils.cloneISwearIKnowWhatImDoing(replacement));
107
+ } else if (
108
+ parent.type == "MemberExpression"
109
+ && (parent.object.type == "Identifier" && parent.object.name == "module")
110
+ && ((parent.property.type == "Identifier" && parent.property.name == "exports") || (parent.property.type == "Literal" && parent.property.value == "exports"))
111
+ ) {
112
+ this.esutils.replaceNode(ast, parent, utils.cloneISwearIKnowWhatImDoing(replacement));
113
+ }
114
+ });
115
+ });
116
+
117
+ return ast;
118
+ }
119
+
120
+ /**
121
+ * Merges multiple modules into a single main module.
122
+ * @param {Object.<string, Node>} modules Module dictionary
123
+ * @param {string} mainKey Main module key
124
+ * @param {ScopeManager} scopeManager Scope manager
125
+ * @returns {Node} Transformed root node
126
+ */
127
+ merge (modules, mainKey, scopeManager) {
128
+ assert.ok(Object.keys(modules).length > 0);
129
+ assert.equal(typeof mainKey, "string");
130
+
131
+ modules = _.mapKeys(modules, (value, key) => normalizePath(key));
132
+ mainKey = normalizePath(mainKey);
133
+
134
+ var declaration = {
135
+ type: "VariableDeclaration",
136
+ kind: "var",
137
+ declarations: []
138
+ };
139
+ var embeds = [];
140
+
141
+ var rng = new utils.UniqueRandomAlpha(3);
142
+
143
+ var processedModules = {};
144
+
145
+ var requiresOrder = [];
146
+
147
+ function walkDeps(key, stack) {
148
+ stack = stack || [];
149
+
150
+ findRequires(modules[key], node => {
151
+ var path = node.arguments.length > 0 && node.arguments[0].value;
152
+
153
+ if (!path) {
154
+ return node;
155
+ }
156
+
157
+ if (![ "/", "./", "../" ].some(x => path.indexOf(x) == 0)) {
158
+ return node;
159
+ }
160
+
161
+ path = resolvePath(key, path);
162
+
163
+ if (path.slice(-3) == ".js") {
164
+ path = path.slice(0, -3);
165
+ }
166
+
167
+ if (!modules[path]) {
168
+ path = path + ".js";
169
+ }
170
+
171
+ requiresOrder.push(path);
172
+
173
+ var _module = modules[path];
174
+ if (!_module) {
175
+ this.logger.warn(`Local module not found: ${path}`);
176
+ return node;
177
+ }
178
+
179
+ if (stack.indexOf(path) == -1) {
180
+ walkDeps.call(this, path, stack.concat(path));
181
+ } else {
182
+ this.logger.warn("Skipping cyclic depedency: " + path);
183
+ }
184
+
185
+ if (!processedModules[path]) {
186
+ var id = processedModules[path] = "$$module$" + rng.get();
187
+
188
+ declaration.declarations.push({
189
+ type: "VariableDeclarator",
190
+ id: { type: "Identifier", name: id },
191
+ init: { type: "ObjectExpression", properties: [] }
192
+ });
193
+
194
+ _module = this.replaceExportsReferences(_module, { type: "Identifier", name: id });
195
+
196
+ embeds.push({
197
+ type: "ExpressionStatement",
198
+ expression: {
199
+ type: "CallExpression",
200
+ callee: {
201
+ type: "FunctionExpression",
202
+ params: [
203
+ ],
204
+ body: {
205
+ type: "BlockStatement",
206
+ body: _module.body
207
+ }
208
+ },
209
+ arguments: [
210
+
211
+ ]
212
+ },
213
+ veilmark$module: path
214
+ });
215
+ }
216
+
217
+ return { type: "Identifier", name: processedModules[path] };
218
+ });
219
+ }
220
+ // Method has to be called via .call because otherwise this is not being passed correctly for some reason
221
+ walkDeps.call(this, mainKey);
222
+
223
+ // Check whether the VariableDeclaration contains VariableDeclarators, because an empty VariableDeclaration causes errors
224
+ if (declaration.declarations.length > 0) {
225
+ modules[mainKey].body = [ declaration ].concat(embeds).concat(modules[mainKey].body);
226
+ }
227
+
228
+ return modules[mainKey];
229
+ }
230
+
231
+ };