@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,69 @@
1
+ "use strict";
2
+
3
+ var assert = require("assert");
4
+
5
+ var _ = require("lodash");
6
+
7
+ var estest = require("../estest");
8
+ var traverser = require("../traverser");
9
+ var utils = require("../utils");
10
+
11
+ /**
12
+ * Merges nested bind calls like
13
+ * veilmark$bind(veilmark$bind(main, 1234), 5678)
14
+ * to
15
+ * veilmark$bind(main, 1234, 5678)
16
+ * @param {Node} node
17
+ * @returns {Node}
18
+ */
19
+ function mergeNestedBinds(node) {
20
+ assert.ok(estest.isNode(node));
21
+
22
+ if (isBindCall(node)) {
23
+ return mergeNestedBinds(node.arguments[0]).concat(node.arguments.slice(1));
24
+ } else {
25
+ return [ node ];
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Checks whether node is a call to veilmark$bind.
31
+ * @param {Node} node
32
+ * @returns {boolean}
33
+ */
34
+ function isBindCall(node) {
35
+ assert.ok(estest.isNode(node));
36
+
37
+ return node.type == "CallExpression"
38
+ && node.callee.type == "Identifier"
39
+ && node.callee.name == "veilmark$bind";
40
+ }
41
+
42
+ module.exports = class Postprocessing {
43
+
44
+ constructor (logger) {
45
+ this.logger = logger;
46
+ }
47
+
48
+ /**
49
+ * Does postprocessing.
50
+ * @param {Node} ast Root node
51
+ * @return {Node} Root node
52
+ */
53
+ do (ast) {
54
+ assert.ok(estest.isNode(ast));
55
+
56
+ return traverser.traverse(ast, [], (node, stack) => {
57
+ if (isBindCall(node)) {
58
+ node.arguments = mergeNestedBinds(node);
59
+ } else if (node.type == "BlockStatement" || node.type == "Program") {
60
+ node.body = node.body.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
61
+ } else if (node.type == "SwitchCase") {
62
+ node.consequent = node.consequent.filter(x => estest.isNode(x) && x.type != "EmptyStatement");
63
+ }
64
+
65
+ return node;
66
+ });
67
+ }
68
+
69
+ };
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+
3
+ var _ = require("lodash");
4
+
5
+ var { Parser: Parser } = require("expr-eval");
6
+
7
+ const DEFAULT_PREPROCESSOR_VARIABLES = {
8
+ "true": 1,
9
+ "false": 0
10
+ };
11
+
12
+ /**
13
+ * Generates code from an array of text nodes.
14
+ * @param {TextNode[]} nodes
15
+ * @returns {string}
16
+ */
17
+ function codeFromNodeArray(nodes) {
18
+ let lines = [];
19
+ for (let node of nodes) {
20
+ lines[node.line] = node.text;
21
+ }
22
+ return lines.join("\n");
23
+ }
24
+
25
+ /**
26
+ * Removes shebang from beginning of code.
27
+ * @param {string} code
28
+ * @returns {string}
29
+ */
30
+ function removeShebangs(code) {
31
+ if (_.startsWith(code, "#!")) {
32
+ code = code.split(/\r?\n/).slice(1).join("\n");
33
+ }
34
+
35
+ return code;
36
+ }
37
+
38
+ class ArrayUtils {
39
+ /**
40
+ * Replaces all occurences of an object in an array with an other object in place.
41
+ * @param {Array} arr
42
+ * @param {Object} oldElem
43
+ * @param {Object} newElem
44
+ */
45
+ static replace(arr, oldElem, newElem) {
46
+ for (let i = 0; i < arr.length; ++i) {
47
+ if (arr[i] == oldElem) {
48
+ arr[i] = newElem;
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ class Node {
55
+ constructor() {
56
+
57
+ }
58
+ /**
59
+ * Evaluates tree into an array of TextNodes.
60
+ * @param {Object.<string, string>} defines Preprocessor variables
61
+ * @returns {TextNode[]}
62
+ */
63
+ eval(defines) {
64
+ throw new Error("Node.eval() can not be called directly");
65
+ }
66
+ }
67
+
68
+ class BlockNode extends Node {
69
+ constructor() {
70
+ super();
71
+ this.children = [];
72
+ }
73
+ eval(defines) {
74
+ return _.flatten(this.children.map(x => x.eval(defines)));
75
+ }
76
+ }
77
+
78
+ class TextNode extends Node {
79
+ constructor(text) {
80
+ super();
81
+ this.text = text;
82
+ }
83
+ eval(defines) {
84
+ return [this];
85
+ }
86
+ }
87
+
88
+ class DefineNode extends Node {
89
+ constructor(left, right) {
90
+ super();
91
+ this.left = left;
92
+ this.right = right;
93
+ }
94
+ eval(defines) {
95
+ defines[this.left] = this.right;
96
+ return [];
97
+ }
98
+ }
99
+
100
+ class ErrorNode extends Node {
101
+ constructor(message) {
102
+ super();
103
+ this.message = message;
104
+ }
105
+ eval(defines) {
106
+ throw new Error(this.message);
107
+ }
108
+ }
109
+
110
+ class IfBlockNode extends BlockNode {
111
+ constructor(condition) {
112
+ super();
113
+ this.condition = condition;
114
+ }
115
+ /**
116
+ * Evaluates condition.
117
+ * @param {Object.<string, string>} defines Preprocessor variables
118
+ * @returns {boolean}
119
+ */
120
+ evalCond(defines) {
121
+ let condition = this.condition;
122
+ condition = condition.replace(/!defined\(([\w\d]+)\)/, (match, p1) => !defines.hasOwnProperty(p1) ? "true" : "false");
123
+ condition = condition.replace(/defined\(([\w\d]+)\)/, (match, p1) => defines.hasOwnProperty(p1) ? "true" : "false");
124
+ return Parser.evaluate(condition, defines);
125
+ }
126
+ /**
127
+ * Evaluates node with given condition result.
128
+ * @param {Object.<string, string>} defines Preprocessor variables
129
+ * @returns {boolean}
130
+ */
131
+ evalWith(defines, result) {
132
+ if (result) {
133
+ return super.eval(defines);
134
+ } else {
135
+ return [];
136
+ }
137
+ }
138
+ eval(defines) {
139
+ return this.evalWith(defines, this.evalCond(defines));
140
+ }
141
+ }
142
+
143
+ class ElseBlockNode extends BlockNode {
144
+ constructor(ifNode) {
145
+ super();
146
+ this.ifNode = ifNode;
147
+ }
148
+ eval(defines) {
149
+ if (this.ifNode.evalCond(defines)) {
150
+ return this.ifNode.evalWith(defines, true);
151
+ } else {
152
+ return super.eval(defines);
153
+ }
154
+ }
155
+ }
156
+
157
+ module.exports = class Preprocessing {
158
+
159
+ constructor (logger) {
160
+ this.logger = logger;
161
+ }
162
+
163
+ /**
164
+ * Processes preprocessor directives.
165
+ * @param {string} code
166
+ * @param {Object.<string, string>} preprocessorVariables
167
+ * @returns {string} Processed code
168
+ */
169
+ processDirectives (code, preprocessorVariables) {
170
+ let lines = code.split(/\r?\n/), stack = [new BlockNode()];
171
+
172
+ let defines = {};
173
+ _.merge(defines, DEFAULT_PREPROCESSOR_VARIABLES);
174
+ _.merge(defines, preprocessorVariables);
175
+
176
+ for (let i = 0; i < lines.length; ++i) {
177
+ let line = lines[i];
178
+ let [, directive, parameters] = /^\s*\/\/\s*#(\w+)\s*(.+)?$/.exec(line) || [];
179
+ switch (directive) {
180
+ case undefined: {
181
+ let elem = new TextNode(line);
182
+ elem.line = i;
183
+ _.last(stack).children.push(elem);
184
+ break;
185
+ }
186
+ case "define": {
187
+ let [, left, right] = /^\s*([\w\d]+)\s*(?:=\s*([\w\d]+))?\s*$/.exec(parameters);
188
+ let elem = new DefineNode(left, right);
189
+ elem.line = i;
190
+ _.last(stack).children.push(elem);
191
+ break;
192
+ }
193
+ case "error": {
194
+ let elem = new ErrorNode(parameters);
195
+ elem.line = i;
196
+ _.last(stack).children.push(elem);
197
+ break;
198
+ }
199
+ case "if":
200
+ case "ifdef":
201
+ case "ifndef": {
202
+ let elem =
203
+ directive == "if" ? new IfBlockNode(parameters) :
204
+ directive == "ifdef" ? new IfBlockNode(`defined(${parameters})`) :
205
+ directive == "ifndef" ? new IfBlockNode(`!defined(${parameters})`) :
206
+ null;
207
+ elem.line = i;
208
+ _.last(stack).children.push(elem);
209
+ stack.push(elem);
210
+ break;
211
+ }
212
+ case "else": {
213
+ let elem = new ElseBlockNode(stack.pop());
214
+ elem.line = i;
215
+ ArrayUtils.replace(_.last(stack).children, elem.ifNode, elem);
216
+ stack.push(elem);
217
+ break;
218
+ }
219
+ case "endif": {
220
+ let elem = stack.pop();
221
+ break;
222
+ }
223
+ default: {
224
+ this.logger.warn(`Unknown preprocessor directive #${directive}`);
225
+ }
226
+ }
227
+ }
228
+
229
+ if (stack.length > 1) {
230
+ this.logger.warn("stack.length != 1 (preprocessor directive closing tag missing?)");
231
+ }
232
+
233
+ return codeFromNodeArray(stack[0].eval(defines));
234
+ }
235
+
236
+ /**
237
+ * Does preprocessing.
238
+ * @param {string} code
239
+ * @param {Object.<string, string>} preprocessorVariables
240
+ * @returns {string}
241
+ */
242
+ process (code, preprocessorVariables) {
243
+ code = this.processDirectives(code, preprocessorVariables);
244
+ code = removeShebangs(code);
245
+ return code;
246
+ }
247
+
248
+ };
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+
3
+ var assert = require("assert");
4
+
5
+ var _ = require("lodash");
6
+
7
+ var estest = require("../estest");
8
+ var ESUtils = require("../esutils");
9
+ var traverser = require("../traverser");
10
+ var utils = require("../utils");
11
+
12
+ module.exports = class Scopes {
13
+
14
+ constructor (logger) {
15
+ this.logger = logger;
16
+ this.esutils = new ESUtils(logger);
17
+ }
18
+
19
+ /**
20
+ * Moves all variables to scope arrays like
21
+ * var a = 1, b = 2;
22
+ * to
23
+ * var $$scope$abc = [];
24
+ * $$scope$abc[0] = 1;
25
+ * $$scope$abc[1] = 2;
26
+ * @param {Node} ast Root node
27
+ * @param {ScopeManager} scopeManager Scope manager
28
+ */
29
+ createScopeObjects (ast, scopeManager) {
30
+ assert.ok(estest.isNode(ast));
31
+
32
+ this.esutils.setParentsRecursive(ast);
33
+ var scopes = scopeManager.acquireAll(ast);
34
+ var rngAlpha = new utils.UniqueRandomAlpha(3);
35
+ scopeManager.scopes.forEach(scope => {
36
+ var scopeVarName = `$$scope$${rngAlpha.get()}`;
37
+
38
+ var counter = 0;
39
+ var scopeDecl = {
40
+ type: "VariableDeclaration",
41
+ kind: "var",
42
+ declarations: [
43
+ {
44
+ type: "VariableDeclarator",
45
+ id: { type: "Identifier", name: scopeVarName },
46
+ init: { type: "ArrayExpression", elements: [] }
47
+ }
48
+ ],
49
+ veilmark$scopeObject: true
50
+ };
51
+
52
+ this.esutils.insertIntoScope(scope, scopeDecl);
53
+
54
+ scope.variables.forEach(variable => {
55
+ var index = counter++;
56
+
57
+ variable.defs.forEach(def => {
58
+ if (def.type == "Variable") {
59
+ assert(def.parent.type == "VariableDeclaration");
60
+ def.parent.declarations = def.parent.declarations.filter(x => x != def.node);
61
+ var replacement = [];
62
+ if (def.node.init) {
63
+ replacement.push({
64
+ type: "ExpressionStatement",
65
+ expression: {
66
+ type: "AssignmentExpression",
67
+ operator: "=",
68
+ left: {
69
+ type: "MemberExpression",
70
+ object: { type: "Identifier", name: scopeVarName },
71
+ property: { type: "Literal", value: index },
72
+ computed: true,
73
+ veilmark$scopeObjectReference: true
74
+ },
75
+ right: def.node.init
76
+ }
77
+ });
78
+ }
79
+ if (def.parent.declarations.length > 0) {
80
+ replacement.push(def.parent);
81
+ }
82
+ if (replacement.length == 0) {
83
+ this.esutils.replaceNode(scope.block, def.parent, { type: "EmptyStatement" });
84
+ } else if (replacement.length == 1) {
85
+ this.esutils.replaceNode(scope.block, def.parent, replacement[0] );
86
+ } else {
87
+ this.esutils.replaceNode(scope.block, def.parent, { type: "BlockStatement", body: replacement });
88
+ }
89
+ variable.references.forEach(reference => {
90
+ // References can not be replaced via replaceNodeEx for whatever reason
91
+ this.esutils.replaceNode(scope.block, reference.identifier, {
92
+ type: "MemberExpression",
93
+ object: { type: "Identifier", name: scopeVarName },
94
+ property: { type: "Literal", value: index },
95
+ computed: true,
96
+ veilmark$scopeObjectReference: true
97
+ });
98
+ });
99
+ } else if (def.type == "CatchClause") {
100
+ Object.defineProperty(scope.block, "veilmark$exception", {
101
+ value: {
102
+ type: "MemberExpression",
103
+ object: { type: "Identifier", name: scopeVarName },
104
+ property: { type: "Literal", value: index },
105
+ computed: true,
106
+ veilmark$scopeObjectReference: true
107
+ },
108
+ configurable: true
109
+ });
110
+ this.esutils.insertIntoScope(scope, {
111
+ type: "ExpressionStatement",
112
+ expression: {
113
+ type: "AssignmentExpression",
114
+ operator: "=",
115
+ left: {
116
+ type: "MemberExpression",
117
+ object: { type: "Identifier", name: scopeVarName },
118
+ property: { type: "Literal", value: index },
119
+ computed: true,
120
+ veilmark$scopeObjectReference: true
121
+ },
122
+ right: def.name
123
+ }
124
+ }, 1);
125
+ variable.references.forEach(reference => {
126
+ this.esutils.replaceNode(scope.block, reference.identifier, {
127
+ type: "MemberExpression",
128
+ object: { type: "Identifier", name: scopeVarName },
129
+ property: { type: "Literal", value: index },
130
+ computed: true,
131
+ veilmark$scopeObjectReference: true
132
+ });
133
+ });
134
+ } else if (def.type == "FunctionName") {
135
+ variable.references.forEach(reference => {
136
+ this.esutils.replaceNode(scope.block, reference.identifier, {
137
+ type: "CallExpression",
138
+ callee: { type: "Identifier", name: "veilmark$bind" },
139
+ arguments: [
140
+ reference.identifier,
141
+ { type: "Identifier", name: scopeVarName }
142
+ ]
143
+ });
144
+ });
145
+ }
146
+ });
147
+ });
148
+
149
+ traverser.traverse(scope.block, [], (node, stack) => {
150
+ if (scope.block == node) {
151
+ return node;
152
+ }
153
+
154
+ if (node.type.indexOf("Function") == 0) {
155
+ node.params.unshift({
156
+ type: "Identifier",
157
+ name: scopeVarName
158
+ });
159
+ }
160
+
161
+ if (node.type == "FunctionExpression") {
162
+ return {
163
+ type: "CallExpression",
164
+ callee: { type: "Identifier", name: "veilmark$bind" },
165
+ arguments: [
166
+ node,
167
+ { type: "Identifier", name: scopeVarName }
168
+ ]
169
+ };
170
+ }
171
+
172
+ return node;
173
+ });
174
+ });
175
+ }
176
+
177
+ };
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ var assert = require("assert");
4
+
5
+ var esshorten = require("esshorten");
6
+
7
+ var estest = require("../estest");
8
+
9
+ module.exports = class Uglifier {
10
+
11
+ constructor (logger) {
12
+ this.logger = logger;
13
+ }
14
+
15
+ /**
16
+ * Uglifies tree.
17
+ * @param {Node} ast Root node
18
+ * @returns {Node} Root node
19
+ */
20
+ uglify (ast) {
21
+ assert.ok(estest.isNode(ast));
22
+
23
+ return esshorten.mangle(ast);
24
+ }
25
+
26
+ };
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+
3
+ var assert = require("assert");
4
+
5
+ var _ = require("lodash");
6
+
7
+ var estest = require("../estest");
8
+ var ESUtils = require("../esutils");
9
+ var traverser = require("../traverser");
10
+ var utils = require("../utils");
11
+
12
+ module.exports = class Variables {
13
+
14
+ constructor (logger) {
15
+ this.logger = logger;
16
+ this.esutils = new ESUtils(logger);
17
+ }
18
+
19
+ /**
20
+ * Removes the id property from FunctionExpressions.
21
+ * They trip up functionDeclarationToExpression and escope (?),
22
+ * causing scopes.js to incorrectly rename some references.
23
+ * @param {Node} ast Root node
24
+ * @returns {Node} Root node
25
+ */
26
+ removeFunctionExpressionIds (ast) {
27
+ return traverser.traverse(ast, [], (node, stack) => {
28
+ if (node.type == "FunctionExpression" && node.id) {
29
+ node.id = null;
30
+ }
31
+ return node;
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Converts function declarations like
37
+ * function test() { ... }
38
+ * to function expression variables like
39
+ * var test = function() { ... };
40
+ * @param {Node} ast Root node
41
+ * @param {ScopeManager} scopeManager Scope manager
42
+ */
43
+ functionDeclarationToExpression (ast, scopeManager) {
44
+ assert.ok(estest.isNode(ast));
45
+
46
+ this.esutils.setParentsRecursive(ast);
47
+
48
+ scopeManager.scopes.forEach(scope => {
49
+ scope.variables.forEach(variable => {
50
+ variable.defs.forEach(def => {
51
+ if (def.type == "FunctionName") {
52
+ assert(estest.isFunction(def.node));
53
+ /**
54
+ * Here you have to ensure that def.node is statement.
55
+ * Expressions like { foo: function() { ... }} are parsed
56
+ * as a FunctionExpression with an id, which are then
57
+ * mistakingly replaced with EmptyStatements.
58
+ */
59
+ if (estest.isStatement(def.node)) {
60
+ this.esutils.replaceNode(ast, def.node, { type: "EmptyStatement" });
61
+ this.esutils.insertIntoScope(scope, {
62
+ type: "VariableDeclaration",
63
+ kind: "var",
64
+ declarations: [
65
+ {
66
+ type: "VariableDeclarator",
67
+ id: def.node.id,
68
+ init: {
69
+ type: "FunctionExpression",
70
+ params: def.node.params,
71
+ body: def.node.body
72
+ }
73
+ }
74
+ ]
75
+ });
76
+ }
77
+ }
78
+ });
79
+ });
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Renames identifiers with unique names, e.g.
85
+ * var a, b = 5;
86
+ * to
87
+ * var $$var$123$a, $$var$123$b = 5;
88
+ * @param {Node} ast Root node
89
+ * @param {ScopeManager} scopeManager Scope manager
90
+ */
91
+ obfuscateIdentifiers (ast, scopeManager) {
92
+ scopeManager.scopes.forEach(scope => {
93
+ if (scope.isStatic()) {
94
+ scope.variables.sort((a, b) => {
95
+ if (a.tainted) {
96
+ return 1;
97
+ }
98
+ if (b.tainted) {
99
+ return -1;
100
+ }
101
+ return (b.identifiers.length + b.references.length) - (a.identifiers.length + a.references.length);
102
+ });
103
+
104
+ for (let variable of scope.variables) {
105
+ var name = "$$var$" + utils.hash(variable) + "$" + variable.name;
106
+
107
+ if (variable.tainted) {
108
+ continue;
109
+ }
110
+
111
+ if (variable.identifiers.length === 0) {
112
+ // do not change names since this is a special name
113
+ continue;
114
+ }
115
+
116
+ for (let def of variable.identifiers) {
117
+ // change definition's name
118
+ def.name = name;
119
+ }
120
+
121
+ for (let ref of variable.references) {
122
+ // change reference's name
123
+ ref.identifier.name = name;
124
+ }
125
+ }
126
+ }
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Replaces direct parameter references like
132
+ * function (a) {
133
+ * return a;
134
+ * }
135
+ * to copys like
136
+ * function (a) {
137
+ * var $$arg$abc = a;
138
+ * return $$arg$abc;
139
+ * }
140
+ * @param {Node} ast Root node
141
+ * @param {ScopeManager} scopeManager Scope manager
142
+ */
143
+ redefineParameters (ast, scopeManager) {
144
+ function getArgumentIndex(method, identifier) {
145
+ assert(method.type == "FunctionDeclaration" || method.type == "FunctionExpression");
146
+ assert(identifier.type == "Identifier");
147
+ for (var i = 0; i < method.params.length; ++i) {
148
+ if (method.params[i].name == identifier.name) {
149
+ return i;
150
+ }
151
+ }
152
+ return -1;
153
+ }
154
+
155
+ var rng = new utils.UniqueRandomAlpha(3);
156
+
157
+ scopeManager.scopes.forEach(scope => {
158
+ scope.variables.forEach(variable => {
159
+ variable.defs.forEach(def => {
160
+ if (def.type == "Parameter") {
161
+ assert(def.name.type == "Identifier");
162
+ var name = "$$arg$" + rng.get();
163
+
164
+ this.esutils.insertIntoScope(scope, {
165
+ type: "VariableDeclaration",
166
+ kind: "var",
167
+ declarations: [
168
+ {
169
+ type: "VariableDeclarator",
170
+ id: { type: "Identifier", name: name },
171
+ init: def.name
172
+ }
173
+ ]
174
+ });
175
+
176
+ variable.references.forEach(reference => {
177
+ reference.identifier.name = name;
178
+ });
179
+ }
180
+ });
181
+ });
182
+ });
183
+ }
184
+
185
+ };