aboutsummaryrefslogtreecommitdiffstats
path: root/lib/js_checker
diff options
context:
space:
mode:
authorNik Nyby <nikolas@gnu.org>2015-01-17 17:12:36 -0500
committerNik Nyby <nikolas@gnu.org>2015-01-17 17:12:36 -0500
commitada88090ead2c3b9d0804794c5f20f9b24d1c2b1 (patch)
tree2838a7eee6c5d74094216acebd86915e0ea1de42 /lib/js_checker
downloadlibrejsxul-ada88090ead2c3b9d0804794c5f20f9b24d1c2b1.tar.lz
librejsxul-ada88090ead2c3b9d0804794c5f20f9b24d1c2b1.tar.xz
librejsxul-ada88090ead2c3b9d0804794c5f20f9b24d1c2b1.zip
Import to new git repository
The old repository was using almost 100mb of space because of all the unnecessary files in the history. So I've imported the code to a new git repository. Unfortunately the history isn't viewable from this repository anymore. To see what happened with LibreJS before 2015, see the old Bazaar repo here: http://bzr.savannah.gnu.org/lh/librejs/
Diffstat (limited to 'lib/js_checker')
-rw-r--r--lib/js_checker/constant_types.js190
-rw-r--r--lib/js_checker/free_checker.js231
-rw-r--r--lib/js_checker/js_checker.js518
-rw-r--r--lib/js_checker/license_definitions.js249
-rw-r--r--lib/js_checker/nontrivial_checker.js374
-rw-r--r--lib/js_checker/pattern_utils.js40
-rw-r--r--lib/js_checker/privacy_checker.js44
-rw-r--r--lib/js_checker/privacy_threat_definitions.js52
-rw-r--r--lib/js_checker/relation_checker.js289
9 files changed, 1987 insertions, 0 deletions
diff --git a/lib/js_checker/constant_types.js b/lib/js_checker/constant_types.js
new file mode 100644
index 0000000..59e7489
--- /dev/null
+++ b/lib/js_checker/constant_types.js
@@ -0,0 +1,190 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+//var debug = require("debug/debug");
+
+// token list from exports.init();
+exports.token = {
+ END: 0,
+ NEWLINE: 1,
+ SEMICOLON: 2,
+ COMMA: 3,
+ ASSIGN: 4,
+ HOOK: 5,
+ COLON: 6,
+ CONDITIONAL: 7,
+ OR: 8,
+ AND: 9,
+ BITWISE_OR: 10,
+ BITWISE_XOR: 11,
+ BITWISE_AND: 12,
+ EQ: 13,
+ NE: 14,
+ STRICT_EQ: 15,
+ STRICT_NE: 16,
+ LT: 17,
+ LE: 18,
+ GE: 19,
+ GT: 20,
+ LSH: 21,
+ RSH: 22,
+ URSH: 23,
+ PLUS: 24,
+ MINUS: 25,
+ MUL: 26,
+ DIV: 27,
+ MOD: 28,
+ NOT: 29,
+ BITWISE_NOT: 30,
+ UNARY_PLUS: 31,
+ UNARY_MINUS: 32,
+ INCREMENT: 33,
+ DECREMENT: 34,
+ DOT: 35,
+ LEFT_BRACKET: 36,
+ RIGHT_BRACKET: 37,
+ LEFT_CURLY: 38,
+ RIGHT_CURLY: 39,
+ LEFT_PAREN: 40,
+ RIGHT_PAREN: 41,
+ SCRIPT: 42,
+ BLOCK: 43,
+ LABEL: 44,
+ FOR_IN: 45,
+ CALL: 46,
+ NEW_WITH_ARGS: 47,
+ INDEX: 48,
+ ARRAY_INIT: 49,
+ OBJECT_INIT: 50,
+ PROPERTY_INIT: 51,
+ GETTER: 52,
+ SETTER: 53,
+ GROUP: 54,
+ LIST: 55,
+ LET_BLOCK: 56,
+ ARRAY_COMP: 57,
+ GENERATOR: 58,
+ COMP_TAIL: 59,
+ IDENTIFIER: 60,
+ NUMBER: 61,
+ STRING: 62,
+ REGEXP: 63,
+ BREAK: 64,
+ CASE: 65,
+ CATCH: 66,
+ CONST: 67,
+ CONTINUE: 68,
+ DEBUGGER: 69,
+ DEFAULT: 70,
+ DELETE: 71,
+ DO: 72,
+ ELSE: 73,
+ EXPORT: 74,
+ FALSE: 75,
+ FINALLY: 76,
+ FOR: 77,
+ FUNCTION: 78,
+ IF: 79,
+ IMPORT: 80,
+ IN: 81,
+ INSTANCEOF: 82,
+ LET: 83,
+ MODULE: 84,
+ NEW: 85,
+ NULL: 86,
+ RETURN: 87,
+ SWITCH: 88,
+ THIS: 89,
+ THROW: 90,
+ TRUE: 91,
+ TRY: 92,
+ TYPEOF: 93,
+ VAR: 94,
+ VOID: 95,
+ YIELD: 96,
+ WHILE: 97,
+ WITH: 98
+};
+
+var checkTypes = {
+ // trivial.
+ TRIVIAL: 1,
+
+ // defines functions, and so might or might not
+ // be trivial in the end.
+ TRIVIAL_DEFINES_FUNCTION: 2,
+
+ NONTRIVIAL: 3,
+
+ // Free
+ FREE: 4,
+ FREE_SINGLE_ITEM: 5,
+ WHITELISTED: 6
+};
+
+exports.checkTypes = checkTypes;
+
+exports.emptyTypeObj = function() {
+ return {
+ 'type': null,
+ 'reason': null
+ };
+};
+
+exports.nontrivialWithComment = function(comment) {
+ return {
+ 'type': checkTypes.NONTRIVIAL,
+ 'reason': comment
+ };
+};
+
+exports.trivialWithComment = function(comment) {
+ return {
+ 'type': checkTypes.TRIVIAL,
+ 'reason': comment
+ };
+};
+
+exports.trivialFuncWithComment = function(comment) {
+ return {
+ 'type': checkTypes.TRIVIAL_DEFINES_FUNCTION,
+ 'reason': comment
+ };
+};
+
+exports.freeWithComment = function(comment) {
+ return {
+ 'type': checkTypes.FREE,
+ 'reason': comment
+ };
+};
+
+exports.singleFreeWithComment = function(comment) {
+ return {
+ 'type': checkTypes.FREE_SINGLE_ITEM,
+ 'reason': comment
+ };
+};
+
+exports.whitelisted = function(comment) {
+ return {
+ 'type': checkTypes.WHITELISTED,
+ 'reason': comment + ' -- whitelisted by user'
+ };
+};
diff --git a/lib/js_checker/free_checker.js b/lib/js_checker/free_checker.js
new file mode 100644
index 0000000..9aaa262
--- /dev/null
+++ b/lib/js_checker/free_checker.js
@@ -0,0 +1,231 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+var licenses = require('js_checker/license_definitions');
+var simpleStorage = require("sdk/simple-storage");
+const LAZY = licenses.types.LAZY;
+var licenseRegex = [];
+const END_OF_SCRIPT = require('html_script_finder/bug_fix').END_OF_SCRIPT;
+const types = require("js_checker/constant_types");
+
+const token = types.token;
+
+var patternUtils = require('js_checker/pattern_utils').patternUtils;
+
+var licStartLicEndRe = /@licstartThefollowingistheentirelicensenoticefortheJavaScriptcodeinthis(?:page|file)(.*)?@licendTheaboveistheentirelicensenoticefortheJavaScriptcodeinthis(?:page|file)/mi;
+var licenseMagnet = /.*@license ?(magnet\:\?xt=urn\:btih\:[0-9A-Za-z]+).*/;
+var licenseEndMagnet = /.*@license-end.*/i;
+exports.freeCheck = {
+ initLicenses: function (licenses) {
+ for (var item in licenses) {
+ this.stripLicenseToRegexp(licenses[item]);
+ }
+ },
+
+ /**
+ * stripLicenseToRegexp
+ *
+ * Removes all non-alphanumeric characters except for the
+ * special tokens, and replace the text values that are
+ * hardcoded in license_definitions.js
+ *
+ */
+ stripLicenseToRegexp: function (license) {
+ var i = 0,
+ max = license.licenseFragments.length,
+ item;
+
+ for (; i < max; i++) {
+ item = license.licenseFragments[i];
+ item.regex = patternUtils.removeNonalpha(item.text);
+
+ if (license.licenseFragments[i].type === LAZY) {
+
+ // do not permit words before. Since "Not" could be added
+ // and make it nonfree. e.g.: Not licensed under the GPLv3.
+ item.regex = '^(?!.*not).*' + item.regex;
+
+ }
+
+ item.regex = new RegExp(
+ patternUtils.replaceTokens(item.regex), 'i');
+ }
+
+ return license;
+ },
+
+ /**
+ * checkNodeFreeLicense
+ *
+ * Check if the node mentions a free license
+ * in one of its comments.
+ *
+ */
+ checkNodeFreeLicense: function (n, queue) {
+ var strippedComment,
+ magnetLink,
+ comment = this.getComment(n),
+ list = licenses.licenses,
+ i, j,
+ max,
+ regex,
+ frag,
+ matchLicStart,
+ matchMagnet,
+ license,
+ isMagnetValid = false;
+
+ if (n.counter === 2 &&
+ n.parent != undefined &&
+ n.parent.type === token.SCRIPT &&
+ comment != undefined &&
+ comment != " "
+ ) {
+ strippedComment = patternUtils.removeNonalpha(comment);
+ matchLicStart = strippedComment.match(licStartLicEndRe);
+ console.debug("matchMagnet is", matchMagnet);
+ if (matchLicStart) {
+ strippedComment = matchLicStart[1];
+ for (license in list) {
+ frag = list[license].licenseFragments;
+ max = list[license].licenseFragments.length;
+ for (i = 0; i < max; i++) {
+ if (frag[i].regex.test(strippedComment)) {
+ return {
+ licenseName: list[license].licenseName,
+ type: types.checkTypes.FREE
+ };
+
+ }
+ }
+ }
+ }
+ // check for @license -- @license-end notation.
+ return this.matchMagnet(comment, queue);
+ }
+ },
+
+ /**
+ * matchMagnet
+ * Attempts to find valid @license [magnet]
+ * and @license-end notation.
+ */
+ matchMagnet: function (comment, queue) {
+ let matchMagnet = comment.match(licenseMagnet);
+ if (matchMagnet) {
+ let magnetLinkRe = new RegExp(
+ matchMagnet[1].replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
+ );
+ let list = licenses.licenses;
+ let queue_end = queue.length;
+
+ for (var license in list) {
+ frag = list[license].canonicalUrl;
+ console.debug("frag is ", frag);
+ if (frag != undefined) {
+ max = list[license].canonicalUrl.length;
+ console.debug("max is", max);
+ for (i = 0;i < max; i++) {
+ console.debug("current frag is", frag[i]);
+ if (frag[i].match(magnetLinkRe)) {
+ for (let i = 0; i < queue_end; i++) {
+ console.debug(queue[i]);
+ let n = queue[i];
+ comment = this.getComment(n);
+ if (comment != undefined &&
+ comment.match(licenseEndMagnet) &&
+ this.checkIsLastNode(n)
+ ) {
+ // found a closing match. Just accept this script.
+ return {
+ licenseName: list[license].licenseName,
+ type: types.checkTypes.FREE_SINGLE_ITEM
+ };
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return;
+ },
+
+ /**
+ * checkIsLastJsNode.
+ * returns true if n is the last node.
+ * Or if nodes before it are only comments etc (not valid code.)
+ * A special LibreJS node is appended at the end of a script tree to
+ * check if this is the last (and also for narcissus to keep the last comment
+ * in the tree.)
+ * TODO: Refactor LibreJS so that END nodes can have a comment.
+ */
+ checkIsLastNode: function (n) {
+ // first check if the comment is part of the very last statement.
+ if (n.value == "this" && n.next == undefined) {
+ // just make sure the last node is indeed our harmless bit of
+ // js.
+ if (n.tokenizer) {
+ let source = n.tokenizer.source;
+ let substring = source.substr(n.start, n.end+23);
+ if (substring == END_OF_SCRIPT) {
+ return true;
+ }
+ else {
+ console.debug("substring is ", substring);
+ return false;
+ }
+ }
+ console.debug("Hurra! This is the end of our script");
+ return true;
+ }
+
+ // isn't our last node.
+ return false;
+ },
+
+ /**
+ * getComment
+ *
+ * Grab the comment(s) from the node. Concatenates
+ * multiple comments.
+ *
+ */
+ getComment: function (n) {
+ var i = 0, length, comment = "";
+
+ if (n.blockComments == undefined || n.blockComments == " ") {
+ return;
+ }
+
+ length = n.blockComments.length;
+ if (length > 0) {
+ for (; i < length; i++) {
+ comment += n.blockComments[i];
+ }
+ }
+ if (comment == "") {
+ return;
+ }
+ return comment;
+ }
+};
+
+exports.freeCheck.initLicenses(licenses.licenses);
diff --git a/lib/js_checker/js_checker.js b/lib/js_checker/js_checker.js
new file mode 100644
index 0000000..0cf06e3
--- /dev/null
+++ b/lib/js_checker/js_checker.js
@@ -0,0 +1,518 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+var {Cc, Ci, Cu, Cm, Cr} = require("chrome");
+
+var narcissusWorker = require("parser/narcissus_worker")
+ .narcissusWorker;
+
+const nonTrivialModule = require("js_checker/nontrivial_checker");
+const freeChecker = require("js_checker/free_checker");
+const relationChecker = require("js_checker/relation_checker");
+const types = require("js_checker/constant_types");
+
+const scriptsCached = require("script_entries/scripts_cache").scriptsCached;
+var isDryRun = require("addon_management/prefchange").isDryRun;
+
+var checkTypes = types.checkTypes;
+
+const token = types.token;
+
+// for setTimeout.
+const timer = require("sdk/timers");
+
+var callbackMap = {};
+
+/**
+ *
+ * Pairs a hash with a given callback
+ * method from an object.
+ *
+ */
+var setHashCallback = function(hash, callback, notification) {
+ console.debug('setHashCallback', hash);
+ if (hash in callbackMap && isDryRun()) {
+ // workaround for issue with dryrun after checking box.
+ // do nothing.
+ callbackMap[hash] = callback;
+ } else if (hash in callbackMap) {
+ console.debug("callback", callbackMap[hash]);
+ if (notification && typeof notification.close === 'function') {
+ notification.close();
+ }
+ throw Error("already being checked.");
+ } else {
+ console.debug('setting callbackMap for', hash, 'to', callback);
+ callbackMap[hash] = callback;
+ }
+ console.debug("callback is type: ", callback.constructor);
+ //callbackMap[hash] = callback;
+};
+
+var removeHashCallback = function(hash) {
+ if (hash in callbackMap) {
+ delete callbackMap[hash];
+ }
+};
+
+/**
+ * find callback and return result (parse tree).
+ *
+ */
+exports.callbackHashResult = function(hash, result) {
+ console.debug('typeof callbackMap function:', typeof callbackMap[hash]);
+ console.debug('for hash', hash);
+ try {
+ callbackMap[hash](result, hash);
+ } catch (x) {
+ console.debug('error in jsChecker', x, 'hash:', hash);
+ // return tree as false.
+ console.debug("Error with", x);
+ if (typeof callbackMap[hash] === 'function') {
+ callbackMap[hash](false, hash);
+ } else {
+ console.debug('callbackHashResult Error', x);
+ }
+ }
+ // remove callback after it's been called.
+ console.debug('JsChecker.callbackHashResult: calling removeHashCallback');
+ removeHashCallback(hash);
+};
+
+var JsChecker = function() {
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.nonTrivialChecker = null;
+ this.freeToken = false;
+ this.nontrivialness = false;
+ this.parseTree = null;
+ this.relationChecker = null;
+ this.jsCode = null;
+ this.resultReady = null;
+ this.notification = null;
+ this.walkTreeCancelled = false;
+ this.shortText = null;
+ this.hash = null;
+ this.queue = null; // will contain the nodes of the script.
+};
+
+/**
+ * searchJs
+ *
+ * Takes in some javascript code (as string).
+ * Uses Narcissus parser to build an abstract syntax tree.
+ * Checks for trivialness.
+ *
+ */
+JsChecker.prototype.searchJs = function(jsCode, resultReady, url) {
+ var that = this;
+ var bugfix = require('html_script_finder/bug_fix').narcissusBugFixLibreJS;
+ console.debug('JsChecker.searchJs for script url:', url);
+ this.url = url;
+ this.resultReady = resultReady;
+ this.jsCode = jsCode;
+ this.shortText = jsCode.replace(bugfix, '').substring(0,100);
+ this.notification = require("ui/notification")
+ .createNotification(this.shortText).notification;
+
+ var verbatimCode = this.jsCode.replace(bugfix, '');
+ this.hash = scriptsCached.getHash(verbatimCode);
+ var isCached = scriptsCached.isCached(verbatimCode, this.hash);
+ if (isCached) {
+ console.debug("We have it cached indeed!");
+ // there is an existing entry for this exact copy
+ // of script text.
+ console.debug('this script result is cached', this.hash,
+ isCached.result.type);
+ console.debug("Return right away");
+ // we are not generating a parse tree.
+ this.parseTree = {};
+ // fake the result is from parse tree.
+ this.parseTree.freeTrivialCheck = isCached.result;
+
+ this.relationChecker = isCached.relationChecker;
+ // leave without doing parsing/analysis part.
+ this.resultReady();
+ this.removeNotification();
+ return;
+ }
+
+ console.debug('url is not cached:', url);
+
+ try {
+ // no cache, continue.
+ this.relationChecker = relationChecker.relationChecker();
+ this.freeToken = types.emptyTypeObj();
+ this.nontrivialness = types.emptyTypeObj();
+
+ // use this.hash to keep track of comments made by the nontrivial
+ // checker code about why/how the code is found to be nontrivial.
+ this.nonTrivialChecker =
+ nonTrivialModule.nonTrivialChecker(this.hash);
+
+ // register callback and hash. So that result
+ // can be passed.
+ setHashCallback(
+ this.hash, this.handleTree.bind(this), this.notification);
+
+ // parse using ChromeWorker.
+ console.debug(
+ 'JsChecker.searchJs(): starting narcissusWorker.parse()');
+ narcissusWorker.parse(this.jsCode, this.hash);
+ } catch (x) {
+ console.debug('error', x);
+ this.handleTree(false, x);
+ this.removeNotification();
+ }
+};
+
+JsChecker.prototype.handleTree = function(tree, errorMessage) {
+ var that = this;
+
+ if (tree == false || tree == undefined) {
+ // error parsing tree. Just return nonfree nontrivial.
+ this.parseTree = {};
+ this.parseTree.freeTrivialCheck = types.nontrivialWithComment(
+ 'error parsing: ' + errorMessage);
+
+ // cache result with hash of script for future checks.
+ scriptsCached.addEntry(this.jsCode, this.parseTree.freeTrivialCheck,
+ this.relationChecker, true, this.url);
+ this.resultReady();
+ } else {
+ try {
+ // no need to keep parseTree in property
+ this.parseTree = {}; //tree;
+ //console.debug(tree);
+ this.walkTree(tree);
+ } catch (x) {
+ console.debug(x, x.lineNumber, x.fileName);
+ }
+ }
+};
+
+/**
+ * getCheckerResult
+ *
+ * Callback to Assign result from walkTree to property.
+ * reset parse tree. create cache entry.
+ *
+ */
+JsChecker.prototype.getCheckerResult = function(result) {
+ // done with parse tree. Get rid of it.
+ this.parseTree = {};
+ this.removeNotification();
+
+ this.parseTree.nonTrivialChecker = this.nonTrivialChecker;
+
+ // actual result stored here. hack since we used parseTree before.
+ this.parseTree.freeTrivialCheck = result;
+
+ // cache result with hash of script for future checks.
+ scriptsCached.addEntry(this.jsCode, this.parseTree.freeTrivialCheck,
+ this.relationChecker, true, this.url);
+
+ this.resultReady();
+};
+
+/**
+ * trivialCheck
+ *
+ * Runs nodes through a series of conditional statements
+ * to find out whether it is trivial or not.
+ *
+ * @param {object} n. The current node being studied.
+ * @param {string} t. The type of node being studied
+ * (initializer, functionbody, try block, ...)
+ *
+ */
+JsChecker.prototype.trivialCheck = function(n) {
+ return this.nonTrivialChecker.checkNontrivial(n);
+};
+
+/**
+ * freeCheck
+ *
+ * Check if comments above current node could be a free licence.
+ * If it is, then the script will be flagged as free.
+ *
+ * @param {object} n. The current node being studied.
+ * (initializer, functionbody, try block, ...)
+ *
+ */
+JsChecker.prototype.freeCheck = function(n, ntype) {
+ var check = freeChecker.freeCheck.checkNodeFreeLicense(n, this.queue);
+ return check;
+};
+
+/**
+ * walkTree
+ *
+ * An iterative functionwalking the parse tree generated by
+ * Narcissus.
+ *
+ * @param {object} node. The original node.
+ *
+ */
+JsChecker.prototype.walkTree = function(node) {
+ var queue = [node];
+ var i,
+ len,
+ n, counter = 0,
+ result,
+ processQueue,
+ that = this;
+
+ this.queue = queue; // set as property.
+
+ // set top node as visited.
+ node.visited = true;
+
+ /**
+ * functionwalking the tree for a given
+ * amount of time, before calling itself again.
+ */
+ processQueue = function() {
+ var nodeResult, end;
+
+ // record start time of functionexecution.
+ var start = Date.now();
+
+ if (that.walkTreeCancelled) {
+ // tree walking already completed.
+ return;
+ }
+
+ while (queue.length) {
+ n = queue.shift();
+ n.counter = counter++;
+ console.debug("Under review", n.type);
+ if (n.children != undefined) {
+ // fetch all the children.
+ len = n.children.length;
+ for (i = 0; i < len; i++) {
+ if (n.children[i] != undefined &&
+ n.children[i].visited == undefined
+ ) {
+ // figure out siblings.
+ if (i > 0) {
+ n.children[i].previous = n.children[i-1];
+ }
+
+ if (i < len) {
+ n.children[i].next = n.children[i+1];
+ }
+ // set parent property.
+ n.children[i].parent = n;
+ n.children[i].visited = true;
+ queue.push(n.children[i]);
+ }
+ }
+ }
+
+ if (n.type != undefined) {
+ // fetch all properties that may have nodes.
+ for (var item in n) {
+ if (item != 'tokenizer' &&
+ item != 'children' &&
+ item != 'length' &&
+ n[item] != null &&
+ typeof n[item] === 'object' &&
+ n[item].type != undefined &&
+ n[item].visited == undefined
+ ) {
+ n[item].visited = true;
+ // set parent property
+ n[item].parent = n;
+ queue.push(n[item]);
+ }
+ }
+ }
+
+ that.checkNode(n);
+
+ if (that.freeToken.type === checkTypes.FREE ||
+ that.freeToken.type === checkTypes.FREE_SINGLE_ITEM
+ ) {
+ // nothing more to look for. We are done.
+ that.walkTreeComplete(that.freeToken);
+ return;
+ } else if (that.nontrivialness.type === checkTypes.NONTRIVIAL) {
+ // nontrivial
+ // we are done.
+ that.walkTreeComplete(that.nontrivialness);
+ return;
+ }
+ // call processQueue again if needed.
+ end = Date.now();
+
+ if (queue.length) {
+ // there are more nodes in the queue.
+
+ if ((end - start) > 30) {
+
+ // been running more than 20ms, pause
+ // for 10 ms before calling processQueue
+ // again.
+ timer.setTimeout(processQueue, 8);
+ return;
+ }
+ } else {
+ // we are done.
+ that.removeNotification();
+ that.walkTreeComplete();
+ return;
+ }
+ }
+ };
+
+ if (node.type === token.SCRIPT) {
+ // this is the global scope.
+ node.global = true;
+ node.parent = null;
+
+ this.relationChecker.storeGlobalDeclarations(node);
+
+ queue.push(node);
+ processQueue();
+ }
+};
+
+/**
+ * set walk tree cancelled bool as true.
+ * the walk tree method won't run after the variable
+ * is set to true.
+ */
+JsChecker.prototype.cancelWalkTree = function() {
+ // prevent any further work on node codes.
+ this.walkTreeCancelled = true;
+};
+
+/**
+ * walkTreeComplete
+ *
+ * Trigger when the walkTree has been completed or
+ * when it has been cut short.
+ *
+ */
+JsChecker.prototype.walkTreeComplete = function(result) {
+ var that = this;
+ this.removeNotification();
+
+ if (this.walkTreeCancelled) {
+ // we already triggered complete.
+ return;
+ }
+
+ // we set the token to cancel further processing.
+ this.cancelWalkTree();
+
+ if (result != undefined) {
+ // walkTree was returned faster, use it instead.
+ this.getCheckerResult(result);
+
+ // we are done.
+ return;
+ }
+
+ // if all code was fully analyzed.
+ if (this.nontrivialness.type === checkTypes.NONTRIVIAL) {
+ this.getCheckerResult(this.nontrivialness);
+ } else if (this.freeToken.type === checkTypes.FREE) {
+ // this is free and may or may not define functions, we don't care.
+ this.getCheckerResult(this.freeToken);
+ } else if (this.nontrivialness.type ===
+ checkTypes.TRIVIAL_DEFINES_FUNCTION) {
+ // trivial scripts should become nontrivial if an external script.
+ // it may or may not be trivial if inline.
+ this.getCheckerResult(this.nontrivialness);
+ } else {
+ // found no nontrivial constructs or free license, so it's
+ // trivial.
+
+ this.getCheckerResult(
+ types.trivialFuncWithComment("This script is trivial"));
+ }
+};
+
+
+/**
+ * checkNode
+ *
+ * checks a single node.
+ *
+ */
+JsChecker.prototype.checkNode = function(n) {
+ var sub;
+ var fc = this.freeCheck(n);
+ var tc = this.trivialCheck(n);
+
+ var nodeResult;
+
+ // check if identifier may be window property (assumption).
+ this.relationChecker.checkIdentifierIsWindowProperty(n);
+
+ /*if (fc) {
+ console.debug("FC is", fc, "type is", fc.type);
+ }*/
+ if (fc && fc.type == checkTypes.FREE) {
+ // this is free!
+ // freeToken is persistent across nodes analyzed and valid
+ // for an entire script.
+ this.freeToken = types.freeWithComment(
+ "Script appears to be free under the following license: " +
+ fc.licenseName);
+ return;
+ } else if (fc && fc.type == checkTypes.FREE_SINGLE_ITEM) {
+ console.debug("free single item");
+ this.freeToken = types.singleFreeWithComment(
+ "Script appears to be free under the following license: " +
+ fc.licenseName);
+ return;
+ }
+
+ if (tc) {
+ if (tc.type === checkTypes.NONTRIVIAL) {
+ // nontrivial_global is deprecated
+ this.nontrivialness = tc;
+ return;
+ } else if (tc.type === checkTypes.TRIVIAL_DEFINES_FUNCTION) {
+ this.nontrivialness = tc;
+ return;
+ }
+ }
+};
+
+JsChecker.prototype.removeNotification = function() {
+ console.debug('JsChecker.removeNotification()');
+ if (this.notification &&
+ typeof this.notification.close === 'function'
+ ) {
+ console.debug('removing', this.shortText);
+ // remove notification early on.
+ this.notification.close();
+ this.notification = null;
+ }
+};
+
+exports.jsChecker = function() {
+ return new JsChecker();
+};
+
+exports.removeHashCallback = removeHashCallback;
diff --git a/lib/js_checker/license_definitions.js b/lib/js_checker/license_definitions.js
new file mode 100644
index 0000000..79e5090
--- /dev/null
+++ b/lib/js_checker/license_definitions.js
@@ -0,0 +1,249 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+exports.types = {
+ SHORT: 'short',
+ LAZY: 'lazy',
+ FULL: 'full'
+};
+
+var type = exports.types;
+
+/**
+ * List of all the licenses.
+ * Currently only short substrings are used with regex.
+ */
+exports.licenses = {
+ CC0: {
+ licenseName: 'Creative Commons CC0 1.0 Universal',
+ canonicalUrl: [
+ 'http://creativecommons.org/publicdomain/zero/1.0/legalcode',
+ 'magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt'
+ ],
+ identifier: 'CC0-1.0',
+ licenseFragments: []
+ },
+
+
+ gplv2: {
+ licenseName: 'GNU General Public License (GPL) version 2',
+ canonicalUrl: [
+ 'http://www.gnu.org/licenses/gpl-2.0.html',
+ 'magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt'
+ ],
+ identifier: 'GNU-GPL-2.0',
+ licenseFragments: [{text: "<THISPROGRAM> is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.", type: type.SHORT},
+ {text:"Alternatively, the contents of this file may be used under the terms of either the GNU General Public License Version 2 or later (the \"GPL\"), or the GNU Lesser General Public License Version 2.1 or later (the \"LGPL\"), in which case the provisions of the GPL or the LGPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of either the GPL or the LGPL, and not to allow others to use your version of this file under the terms of the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL or the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the MPL, the GPL or the LGPL.", type: type.SHORT}]
+ },
+
+ gplv3: {
+ licenseName: 'GNU General Public License (GPL) version 3',
+ canonicalUrl: [
+ 'http://www.gnu.org/licenses/gpl-3.0.html',
+ 'magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt'
+ ],
+ identifier: 'GNU-GPL-3.0',
+ licenseFragments: [
+ {text: "The JavaScript code in this page is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License (GNU GPL) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The code is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. As additional permission under GNU GPL version 3 section 7, you may distribute non-source (e.g., minimized or compacted) forms of that code without the copy of the GNU GPL normally required by section 4, provided you include this license notice and a URL through which recipients can access the Corresponding Source.", type: type.SHORT},
+ {text: "<THISPROGRAM> is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.", type: type.SHORT}]
+ },
+
+ gnuAllPermissive: {
+ licenseName: 'GNU All-Permissive License',
+ licenseFragments: [{text: "Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty.", type: type.SHORT}]
+ },
+
+ apache_2License: {
+ licenseName: 'Apache License, Version 2.0',
+ canonicalUrl: [
+ 'http://www.apache.org/licenses/LICENSE-2.0',
+ 'magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt'
+ ],
+ identifier: 'Apache-2.0',
+ licenseFragments: [{text: "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0", type: type.SHORT}]
+ },
+
+ lgpl21: {
+ licenseName: 'GNU Lesser General Public License, version 2.1',
+ canonicalUrl: [
+ 'http://www.gnu.org/licenses/lgpl-2.1.html',
+ 'magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt'
+ ],
+ identifier: 'GNU-LGPL-2.1',
+ licenseFragments: [{text: "<THISLIBRARY> is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.", type: type.SHORT}]
+ },
+
+ lgplv3: {
+ licenseName: 'GNU Lesser General Public License, version 3',
+ canonicalUrl: [
+ 'http://www.gnu.org/licenses/lgpl-3.0.html',
+ 'magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt'
+ ],
+ identifier: 'GNU-LGPL-3.0',
+ licenseFragments: [{text: "<THISPROGRAM> is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.", type: type.SHORT}]
+ },
+
+ agplv3: {
+ licenseName: 'GNU AFFERO GENERAL PUBLIC LICENSE version 3',
+ canonicalUrl: [
+ 'http://www.gnu.org/licenses/agpl-3.0.html',
+ 'magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt'
+ ],
+ identifier: 'GNU-AGPL-3.0',
+ licenseFragments: [{text: "<THISPROGRAM> is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.", type: type.SHORT}]
+ },
+
+ boostSoftware: {
+ licenseName: 'Boost Software License',
+ canonicalUrl: [
+ 'http://www.boost.org/LICENSE_1_0.txt',
+ 'magnet:?xt=urn:btih:89a97c535628232f2f3888c2b7b8ffd4c078cec0&dn=Boost-1.0.txt'
+ ],
+ licenseFragments: [{text: "Boost Software License <VERSION> <DATE> Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the \"Software\") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following", type: type.SHORT}]
+ },
+
+ bsd3: {
+ licenseName: "BSD 3-Clause License",
+ canonicalUrl: [
+ 'http://opensource.org/licenses/BSD-3-Clause',
+ 'magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt'
+ ],
+ licenseFragments: [{text: "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.", type: type.SHORT}]
+ },
+
+ bsd2: {
+ licenseName: "BSD 2-Clause License",
+ licenseFragments: [{text: "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.", type: type.SHORT}]
+ },
+
+ epl_1_0: {
+ licenseName: "Eclipse Public License Version 1.0",
+ identifier: "EPL-1.0",
+ canonicalUrl: [
+ "http://www.eclipse.org/legal/epl-v10.html",
+ "magnet:?xt=urn:btih:4c6a2ad0018cd461e9b0fc44e1b340d2c1828b22&dn=epl-1.0.txt"
+ ],
+ licenseFragments: [
+ {
+ text: "THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.",
+ type: type.SHORT
+ }
+ ]
+ },
+
+ mozillaPublicLicense_2_0: {
+ licenseName: 'Mozilla Public License Version 2.0',
+ identifier: 'MPL-2.0',
+ canonicalUrl: [
+ 'http://www.mozilla.org/MPL/2.0',
+ 'magnet:?xt=urn:btih:3877d6d54b3accd4bc32f8a48bf32ebc0901502a&dn=mpl-2.0.txt'
+ ],
+ licenseFragments: [{text: "This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.", type: type.SHORT }]
+ },
+
+ expat: {
+ licenseName: 'Expat License (sometimes called MIT Licensed)',
+ identifier: 'Expat',
+ canonicalUrl: [
+ 'http://www.jclark.com/xml/copying.txt',
+ 'magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt'
+ ],
+ licenseFragments: [{text: "Copyright <YEAR> <NAME> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", type: type.SHORT}]
+ },
+
+ X11: {
+ licenseName: 'X11 License',
+ canonicalUrl: [
+ 'magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt'
+ ],
+ licenseFragments: [{text: "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", type: type.SHORT}]
+ },
+
+ XFree86: {
+ licenseName: "XFree86 License",
+ identifier: 'Modified-BSD',
+ canonicalUrl: [
+ 'http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3',
+ 'http://www.xfree86.org/current/LICENSE4.html',
+ 'magnet:?xt=urn:btih:12f2ec9e8de2a3b0002a33d518d6010cc8ab2ae9&dn=xfree86.txt'
+ ],
+ licenseFragments: [{text: "All rights reserved.\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution, and in the same place and form as other copyright, license and disclaimer information.\n3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: \"This product includes software developed by The XFree86 Project, Inc (http://www.xfree86.org/) and its contributors\", in the same place and form as other third-party acknowledgments. Alternately, this acknowledgment may appear in the software itself, in the same form and location as other such third-party acknowledgments.4. Except as contained in this notice, the name of The XFree86 Project, Inc shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from The XFree86 Project, Inc.", type: type.SHORT}
+ ]
+ },
+
+ FreeBSD: {
+ licenseName: "FreeBSD License",
+ canonicalUrl: [
+ 'http://www.freebsd.org/copyright/freebsd-license.html',
+ 'magnet:?xt=urn:btih:87f119ba0b429ba17a44b4bffcab33165ebdacc0&dn=freebsd.txt'
+ ],
+ licenseFragments: [{text: "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\nRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.", type: type.SHORT}]
+ },
+
+ ISC: {
+ licenseName: "The ISC License",
+ canonicalUrl: [
+ 'https://www.isc.org/downloads/software-support-policy/isc-license/',
+ 'magnet:?xt=urn:btih:b8999bbaf509c08d127678643c515b9ab0836bae&dn=ISC.txt'
+ ],
+ licenseFragments: [{text: "Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.", type: type.SHORT},
+ {text: "Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.", type: type.SHORT}]
+ },
+
+ jQueryTools: {
+ licenseName: "jQuery Tools",
+ licenseFragments: [{text: 'NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.', type: type.SHORT}]
+ },
+
+ ArtisticLicense2: {
+ licenseName: "Artistic License 2.0",
+ canonicalUrl: [
+ "http://www.perlfoundation.org/artistic_license_2_0",
+ "magnet:?xt=urn:btih:54fd2283f9dbdf29466d2df1a98bf8f65cafe314&dn=artistic-2.0.txt"
+ ],
+ licenseFragments: []
+ },
+
+ PublicDomain: {
+ licenseName: "Public Domain",
+ canonicalUrl: [
+ 'magnet:?xt=urn:btih:e95b018ef3580986a04669f1b5879592219e2a7a&dn=public-domain.txt'
+ ],
+ licenseFragments: []
+ },
+
+ CPALv1: {
+ licenseName: 'Common Public Attribution License Version 1.0 (CPAL)',
+ canonicalUrl: [
+ 'http://opensource.org/licenses/cpal_1.0',
+ 'magnet:?xt=urn:btih:84143bc45939fc8fa42921d619a95462c2031c5c&dn=cpal-1.0.txt'
+ ],
+ identifier: 'CPAL-1.0',
+ licenseFragments: [
+ {
+ text: 'The contents of this file are subject to the Common Public Attribution License Version 1.0',
+ type: type.SHORT
+ },
+ {
+ text: 'The term "External Deployment" means the use, distribution, or communication of the Original Code or Modifications in any way such that the Original Code or Modifications may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Code or Modifications as a distribution under section 3.1 and make Source Code available under Section 3.2.',
+ type: type.SHORT
+ }
+ ]
+ }
+};
diff --git a/lib/js_checker/nontrivial_checker.js b/lib/js_checker/nontrivial_checker.js
new file mode 100644
index 0000000..4d3bc8b
--- /dev/null
+++ b/lib/js_checker/nontrivial_checker.js
@@ -0,0 +1,374 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+const types = require("js_checker/constant_types");
+
+// constants from Narcissus for function types.
+const DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2;
+
+const token = types.token;
+
+var checkTypes = types.checkTypes;
+
+var utils = {
+ /**
+ * nodeContains
+ * Checks that node contains both a type and a value.
+ * Shortcut to check for null/undefined.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if matching.
+ */
+ nodeContains: function (n, type, value) {
+ if (n != undefined) {
+ return n.type === type &&
+ n.value === value;
+ }
+ },
+
+ /**
+ * isType
+ * Checks that node is of a certain type.
+ * Shortcut to check for null/undefined.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean}. True if it's the right type.
+ */
+ isType: function (n, type) {
+ return n != undefined && n.type === type;
+ },
+
+ isNotType: function (n, type) {
+ return n != undefined && n.type !== type;
+ },
+
+ /**
+ * hasChildren
+ *
+ * Checks the token on the left
+ * and on the right.
+ *
+ * @param {object} n. The current node being studied.
+ * @param {leftType} token constant. The type on the
+ * left.
+ * @param {rightType} token constant. The type of child
+ * on the right
+ *
+ */
+ hasChildren: function (n, leftType, rightType) {
+ if (types == undefined) {
+ return false;
+ }
+ return this.isType(n.children[0], leftType) &&
+ this.isType(n.children[1], rightType);
+ },
+
+ /**
+ * findScriptTag
+ *
+ * This method should probably be replaced with DOM testing
+ * as regex is rather insufficiant, and this wouldn't cover
+ * tricky constructs as shown in http://ha.ckers.org/xss.html.
+ */
+ findScriptTag: function (n) {
+ return n.value != undefined &&
+ /<script[^>]*?>/i.test(n.value);
+ }
+};
+
+var NonTrivialChecker = function() {
+ this.definesFunction = false;
+ this.hash = null;
+};
+
+/**
+ * definesFunctionFound
+ *
+ * Returns true if it finds a node of type FUNCTION
+ * that isn't a callback or an IIFE.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ */
+NonTrivialChecker.prototype.definesFunctionFound = function (n) {
+ var isFunction = false;
+ if (n.type === token.FUNCTION &&
+ n.body != undefined) {
+
+ if (n.functionForm !== token.DECLARED_FORM &&
+ ((n.parent.type === token.LIST &&
+ n.parent.parent.type === token.CALL) ||
+ n.parent.type === token.CALL) &&
+ n.name == undefined) {
+ // this is a callback or an immediately
+ // invoked function expression "IIFE".
+ isFunction = false;
+ } else {
+ // this is a regular function declaration or
+ // function expression assigned to a variable.
+ //console.log("THIS DEFINES FUNCTION");
+ isFunction = true;
+ }
+ }
+
+ // look for Function constructor.
+ if (n.type === token.IDENTIFIER &&
+ n.value === 'Function' &&
+ (n.parent.type === token.NEW_WITH_ARGS ||
+ n.parent.type === token.CALL)) {
+ // this is a Function constructor.
+ //console.log("THIS DEFINES FUNCTION");
+ isFunction = true;
+ }
+
+ return isFunction;
+};
+
+
+/**
+ * invokesEval
+ *
+ * Returns true (nontrivial) if it finds any use of
+ * the eval function. For simplicity, we assume any
+ * use of an identifier "eval" is the eval function.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ */
+NonTrivialChecker.prototype.invokesEval = function (n) {
+ return (n.type === token.CALL &&
+ utils.nodeContains(n.children[0], token.IDENTIFIER, 'eval') ||
+ n.type === token.IDENTIFIER && n.value === 'eval');
+};
+
+/**
+ * evalIdentifier
+ *
+ * Returns true (nontrivial) if it finds any use of
+ * the eval function. For simplicity, we assume any
+ * use of an identifier "eval" is the eval function.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ */
+NonTrivialChecker.prototype.evalIdentifier = function (n) {
+ return n.type === token.IDENTIFIER && n.value === 'eval';
+};
+
+
+/**
+ * invokesMethodBracketSuffix
+ *
+ * Finds a method being invoked using the bracket suffix notation
+ * rather than the dot notation. It is difficult without keeping track of
+ * variable values to check for what method is actually being called.
+ * So we're just flagging any use of this construct as nontrivial.
+ * e.g., should catch: xhr[a+b]('GET', 'http://www.example.com');
+ * Should not catch other uses such as: myArray[num];
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ */
+NonTrivialChecker.prototype.invokesMethodBracketSuffix = function (n) {
+ return n.type === token.CALL && utils.isType(n.children[0], token.INDEX);
+};
+
+/**
+ * createsXhrObject
+ *
+ * Creates an xhr object.
+ * Since all "new XMLHttpRequest", "XMLHttpRequest()",
+ * and "new window.XMLHttpRequest" instantiate the xhr object,
+ * we assume (without further proof) that any use
+ * of the identifier "XMLHttpRequest" and "ActiveXObject"
+ * is an xhr object.
+ * Constructs like window[a+b]() are already caught by the
+ * bracket suffix check.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ */
+NonTrivialChecker.prototype.createsXhrObject = function (n) {
+ return (n.type === token.IDENTIFIER) &&
+ (n.value === 'XMLHttpRequest' ||
+ n.value === 'ActiveXObject');
+};
+
+/**
+ * invokesXhrOpen
+ *
+ * Here we assume the call of an open method must be an xhr request
+ * (and not some other object) by checking the number of arguments.
+ * In most cases this method won't be used since createsXhrObject
+ * will already have caught the xhr.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ *
+ */
+NonTrivialChecker.prototype.invokesXhrOpen = function (n) {
+ return n.type === token.CALL &&
+ utils.hasChildren(n, token.DOT, token.LIST) &&
+ utils.isType(n.children[0].children[0], token.IDENTIFIER) &&
+ utils.nodeContains(n.children[0].children[1], token.IDENTIFIER, 'open') &&
+ n.children[1].children.length > 1;
+};
+
+/**
+ * createsScriptElement
+ *
+ * Checks for document.createElement() that create a script. In the case
+ * it creates an element from a variable, we assume it's a script. In the
+ * future we might want to check for the value of that string variable
+ * (e.g., if a variable is assigned 'script', raise a flag)
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ *
+ *
+ */
+NonTrivialChecker.prototype.createsScriptElement = function (n) {
+ return n.type === token.CALL &&
+ utils.hasChildren(n, token.DOT, token.LIST) &&
+ utils.isType(n.children[0].children[0], token.IDENTIFIER) &&
+ utils.nodeContains(n.children[0].children[1], token.IDENTIFIER, 'createElement') &&
+ (utils.nodeContains(n.children[1].children[0], token.STRING, 'script') ||
+ utils.isType(n.children[1].children[0], token.IDENTIFIER));
+};
+
+/**
+ * writesScriptAsHtmlString
+ *
+ * catches myObj.write('<script></script>');
+ * or any myObj.write(myStringVariable);
+ * or concatenation such as:
+ * myObj.write('<scri' + stringVariable);
+ * or 'something' + 'somethingelse'.
+ *
+ * To check for javascript here we might want to look at the list
+ * from ha.ckers.org/xss.html for the future.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ *
+ */
+NonTrivialChecker.prototype.writesScriptAsHtmlString = function (n) {
+ var listArg;
+
+ if (n.type === token.CALL &&
+ utils.hasChildren(n, token.DOT, token.LIST) &&
+ utils.isType(n.children[0].children[0], token.IDENTIFIER) &&
+ utils.nodeContains(n.children[0].children[1], token.IDENTIFIER, 'write')
+ ) {
+ if (utils.isNotType(n.children[1].children[0], token.STRING)) {
+ // return true if any operation or concatenation.
+ // We are cautious (as it could
+ // embed a script) and flag this as nontrivial.
+
+ return true;
+ }
+ return utils.findScriptTag(n.children[1].children[0]);
+ } else {
+ return false;
+ }
+};
+
+/**
+ * nontrivial anytime we see an identifier as innerHTML
+ */
+NonTrivialChecker.prototype.innerHTMLIdentifier = function (n) {
+ if ((n.type === token.IDENTIFIER ||
+ n.type === token.STRING) &&
+ n.value === 'innerHTML'
+ ) {
+ return true;
+ }
+};
+
+/**
+ * checkNontrivial
+ *
+ * Contains all the conditionals that try to identify,
+ * step by step, all code that could be flagged as
+ * nontrivial.
+ *
+ * @param {object} n. The current node being studied.
+ * @return {boolean} . True if found.
+ *
+ */
+NonTrivialChecker.prototype.checkNontrivial = function (n, t) {
+
+ if (n.type === token.IDENTIFIER && this.evalIdentifier(n)) {
+ //console.log("NONTRIVIAL: eval has been found in code");
+ return types.nontrivialWithComment("NONTRIVIAL: eval has been found in code");
+ }
+
+ if (this.innerHTMLIdentifier(n)) {
+ //console.log("NONTRIVIAL: innerHTML identifier");
+ return types.nontrivialWithComment("NONTRIVIAL: innerHTML identifier");
+ }
+
+ // the node is an identifier
+ if (n.type === token.IDENTIFIER && this.createsXhrObject(n)) {
+ //console.log('NONTRIVIAL: Creates an xhr object');
+ return types.nontrivialWithComment('NONTRIVIAL: Creates an xhr object');
+ }
+
+ // this is a method/function call
+ if (n.type === token.CALL) {
+
+ if (this.invokesEval(n)) {
+ //console.log("NONTRIVIAL: eval has been found in code");
+ return types.nontrivialWithComment("NONTRIVIAL: eval has been found in code");
+ }
+
+ if (this.invokesMethodBracketSuffix(n)) {
+ //console.log('NONTRIVIAL: square bracket suffix method call detected');
+ return types.nontrivialWithComment("NONTRIVIAL: eval has been found in code");
+ }
+
+ if (this.invokesXhrOpen(n)) {
+ //console.log('NONTRIVIAL: an open method similar to xhr.open is used');
+ return types.nontrivialWithComment('NONTRIVIAL: square bracket suffix method call detected');
+ }
+
+ if (this.createsScriptElement(n)) {
+ //console.log('NONTRIVIAL: creates script element dynamically.');
+ return types.nontrivialWithComment('NONTRIVIAL: an open method similar to xhr.open is used');
+ }
+
+ if (this.writesScriptAsHtmlString(n)) {
+ //console.log('NONTRIVIAL: writes script as html dynamically.');
+ return types.nontrivialWithComment('NONTRIVIAL: creates script element dynamically.');
+ }
+ }
+
+ // The node is a function definition.
+ // Most common occurence.
+ if (this.definesFunctionFound(n)) {
+ return types.trivialFuncWithComment("Script is trivial but defines one or more functions");
+ }
+
+ // found nothing else, so trivial.
+ return types.trivialWithComment("Script is trivial");
+};
+
+exports.nonTrivialChecker = function () {
+ return new NonTrivialChecker();
+};
diff --git a/lib/js_checker/pattern_utils.js b/lib/js_checker/pattern_utils.js
new file mode 100644
index 0000000..00b0c26
--- /dev/null
+++ b/lib/js_checker/pattern_utils.js
@@ -0,0 +1,40 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+exports.patternUtils = {
+ /**
+ * removeNonalpha
+ *
+ * Remove all nonalphanumeric values, except for
+ * < and >, since they are what we use for tokens.
+ *
+ */
+ removeNonalpha: function (str) {
+ var regex = /[^a-z0-9<>@]+/gi;
+ return str.replace(regex, '');
+ },
+ removeWhitespace: function (str) {
+ return str.replace(/\s+/gmi, '');
+ },
+ replaceTokens: function (str) {
+ var regex = /<.*?>/gi;
+ return str.replace(regex, '.*?');
+ }
+};
diff --git a/lib/js_checker/privacy_checker.js b/lib/js_checker/privacy_checker.js
new file mode 100644
index 0000000..7b8917a
--- /dev/null
+++ b/lib/js_checker/privacy_checker.js
@@ -0,0 +1,44 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const privacyThreatJs = require('js_checker/privacy_threat_definitions.js');
+const patternUtils = require('js_checker/pattern_utils').patternUtils;
+
+exports.privacyCheck = {
+ checkScriptPrivacyThreat: function (currentScript) {
+ var list = privacyThreatJs.js;
+ var i;
+ var item;
+ var max;
+
+ currentScript = patternUtils.removeWhitespace(currentScript);
+
+ for (item in list) {
+ max = list[item].length;
+
+ for (i = 0; i < max; i++) {
+ if (list[item][i].test(currentScript)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+};
diff --git a/lib/js_checker/privacy_threat_definitions.js b/lib/js_checker/privacy_threat_definitions.js
new file mode 100644
index 0000000..66b073d
--- /dev/null
+++ b/lib/js_checker/privacy_threat_definitions.js
@@ -0,0 +1,52 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+exports.js = {
+ /**
+ * googleAnalytics
+ * Tracking code for Google Analytics.
+ * It corresponds to:
+ * var _gaq = _gaq || [];
+ * _gaq.push(['_setAccount', 'UA-XXXXXXX-X']);
+ * _gaq.push(['_trackPageview']);
+ *
+ * (function() {
+ * var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ * ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ * var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ * })();
+ *
+ * It also matches GA code that doesn't track a page view, like this:
+ * var _gaq = _gaq || [];
+ * _gaq.push(['_setAccount', 'UA-5936383-6']);
+ *
+ * (function() {
+ * var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ * ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ * var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ * })();
+ *
+ */
+ googleAnalytics: [
+ /var_gaq=_gaq\|\|\[\];_gaq\.push\(\['_setAccount','UA[0-9\-]*?'\]\);(_gaq.push\(\['_setDomainName','[a-z\.]*?'\]\);)?(_gaq\.push\(\['_trackPageview'\]\);)?\(function\(\){varga=document\.createElement\('script'\);ga\.type='text\/javascript\';ga\.async=true;ga\.src=\(\'https:\'==document\.location\.protocol\?'https:\/\/ssl':'http:\/\/www'\)\+'\.google\-analytics\.com\/ga\.js';vars=document\.getElementsByTagName\('script'\)\[0\];s\.parentNode\.insertBefore\(ga,s\);}\)\(\);/i,
+ /vargaJsHost\=\(\(\"https\:\"\=\=document\.location\.protocol\)\?\"https\:\/\/ssl\.\"\:\"http\:\/\/www\.\"\)\;document\.write\(unescape\(\"\%3Cscriptsrc\=\'\"\+gaJsHost\+\"google\-analytics\.com\/ga\.js\'type\=\'text\/javascript\'\%3E\%3C\/script\%3E\"\)\)\;/i,
+ /try{varpageTracker\=\_gat\.\_getTracker\(\"UA[0-9\-]*?\"\)\;pageTracker\.\_trackPageview\(\)\;}catch\(err\){}/i
+ ]
+};
diff --git a/lib/js_checker/relation_checker.js b/lib/js_checker/relation_checker.js
new file mode 100644
index 0000000..1904812
--- /dev/null
+++ b/lib/js_checker/relation_checker.js
@@ -0,0 +1,289 @@
+/**
+ * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
+ * *
+ * Copyright (C) 2011, 2012, 2013, 2014 Loic J. Duros
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/**
+ * relation_checker.js
+ *
+ * Finds out if two scripts are related to each other.
+ *
+ */
+const types = require("js_checker/constant_types");
+
+const token = types.token;
+
+// all predefined window properties (methods and variables).
+const windowPropertiesHash = {
+ "addEventListener": 1, "alert": 1, "applicationCache": 1,
+ "Array": 1, "ArrayBuffer": 1, "atob": 1, "back": 1, "blur": 1,
+ "Boolean": 1, "btoa": 1, "captureEvents": 1, "CharacterData": 1,
+ "clearInterval": 1, "clearTimeout": 1, "close": 1, "closed": 1,
+ "Components": 1, "confirm": 1, "console": 1, "constructor": 1,
+ "content": 1, "controllers": 1, "crypto": 1,
+ "CSSStyleDeclaration": 1, "Date": 1, "decodeURI": 1,
+ "decodeURIComponent": 1, "defaultStatus": 1,
+ "disableExternalCapture": 1, "dispatchEvent": 1, "Document": 1,
+ "document": 1, "DocumentType": 1, "dump": 1, "Element": 1,
+ "enableExternalCapture": 1, "encodeURI": 1, "encodeURIComponent": 1,
+ "Error": 1, "escape": 1, "eval": 1, "EvalError": 1, "Event": 1,
+ "find": 1, "Float32Array": 1, "Float64Array": 1, "focus": 1,
+ "forward": 1, "frameElement": 1, "frames": 1, "fullScreen": 1,
+ "Function": 1, "Generator": 1, "getComputedStyle": 1,
+ "getInterface": 1, "getSelection": 1, "globalStorage": 1,
+ "history": 1, "home": 1, "HTMLBodyElement": 1, "HTMLCollection": 1,
+ "HTMLDivElement": 1, "HTMLDocument": 1, "HTMLElement": 1,
+ "HTMLHeadElement": 1, "HTMLHeadingElement": 1, "HTMLHtmlElement": 1,
+ "HTMLStyleElement": 1, "HTMLUnknownElement": 1, "Infinity": 1,
+ "innerHeight": 1, "innerWidth": 1, "InstallTrigger": 1,
+ "Int16Array": 1, "Int32Array": 1, "Int8Array": 1, "InternalError": 1,
+ "isFinite": 1, "isNaN": 1, "isXMLName": 1, "Iterator": 1,
+ "JSON": 1, "length": 1, "localStorage": 1, "Location": 1,
+ "location": 1, "locationbar": 1, "matchMedia": 1, "Math": 1,
+ "menubar": 1, "moveBy": 1, "moveTo": 1, "mozAnimationStartTime": 1,
+ "mozIndexedDB": 1, "mozInnerScreenX": 1, "mozInnerScreenY": 1,
+ "mozPaintCount": 1, "mozRequestAnimationFrame": 1, "name": 1,
+ "Namespace": 1, "NaN": 1, "navigator": 1, "netscape": 1,
+ "Node": 1, "NodeList": 1, "Number": 1, "Object": 1, "open": 1,
+ "openDialog": 1, "opener": 1, "outerHeight": 1, "outerWidth": 1,
+ "pageXOffset": 1, "pageYOffset": 1, "parent": 1, "parseFloat": 1,
+ "parseInt": 1, "performance": 1, "personalbar": 1, "pkcs11": 1,
+ "postMessage": 1, "print": 1, "prompt": 1, "QName": 1,
+ "RangeError": 1, "ReferenceError": 1, "RegExp": 1,
+ "releaseEvents": 1, "removeEventListener": 1, "resizeBy": 1,
+ "resizeTo": 1, "routeEvent": 1, "screen": 1, "screenX": 1,
+ "screenY": 1, "scroll": 1, "scrollbars": 1, "scrollBy": 1,
+ "scrollByLines": 1, "scrollByPages": 1, "scrollMaxX": 1,
+ "scrollMaxY": 1, "scrollTo": 1, "scrollX": 1, "scrollY": 1,
+ "self": 1, "sessionStorage": 1, "setInterval": 1, "setResizable": 1,
+ "setTimeout": 1, "showModalDialog": 1, "sizeToContent": 1,
+ "status": 1, "statusbar": 1, "stop": 1, "StopIteration": 1,
+ "StorageList": 1, "String": 1, "SyntaxError": 1, "Text": 1,
+ "toolbar": 1, "top": 1, "TypeError": 1, "Uint16Array": 1,
+ "Uint32Array": 1, "Uint8Array": 1, "Uint8ClampedArray": 1,
+ "undefined": 1, "unescape": 1, "uneval": 1, "updateCommands": 1,
+ "URIError": 1, "URL": 1, "WeakMap": 1, "Window": 1, "window": 1,
+ "XML": 1, "XMLList": 1, "XPCNativeWrapper": 1};
+
+// all predefined document properties.
+const documentPropertiesHash = {'activeElement': 1, 'addBinding': 1,
+ 'addEventListener': 1, 'adoptNode': 1, 'alinkColor': 1, 'anchors': 1,
+ 'appendChild': 1, 'applets': 1, 'ATTRIBUTE_NODE': 1, 'attributes': 1,
+ 'baseURI': 1, 'bgColor': 1, 'body': 1, 'captureEvents': 1,
+ 'CDATA_SECTION_NODE': 1, 'characterSet': 1, 'childNodes': 1, 'clear':
+ 1, 'cloneNode': 1, 'close': 1, 'COMMENT_NODE': 1,
+ 'compareDocumentPosition': 1, 'compatMode': 1, 'contentType': 1,
+ 'cookie': 1, 'createAttribute': 1, 'createAttributeNS': 1,
+ 'createCDATASection': 1, 'createComment': 1, 'createDocumentFragment':
+ 1, 'createElement': 1, 'createElementNS': 1, 'createEvent': 1,
+ 'createExpression': 1, 'createNodeIterator': 1, 'createNSResolver': 1,
+ 'createProcessingInstruction': 1, 'createRange': 1, 'createTextNode':
+ 1, 'createTreeWalker': 1, 'currentScript': 1, 'defaultView': 1,
+ 'designMode': 1, 'dir': 1, 'dispatchEvent': 1, 'doctype': 1,
+ 'DOCUMENT_FRAGMENT_NODE': 1, 'DOCUMENT_NODE': 1,
+ 'DOCUMENT_POSITION_CONTAINED_BY': 1, 'DOCUMENT_POSITION_CONTAINS': 1,
+ 'DOCUMENT_POSITION_DISCONNECTED': 1, 'DOCUMENT_POSITION_FOLLOWING': 1,
+ 'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 1,
+ 'DOCUMENT_POSITION_PRECEDING': 1, 'DOCUMENT_TYPE_NODE': 1,
+ 'documentElement': 1, 'documentURI': 1, 'domain': 1, 'ELEMENT_NODE':
+ 1, 'elementFromPoint': 1, 'embeds': 1, 'enableStyleSheetsForSet': 1,
+ 'ENTITY_NODE': 1, 'ENTITY_REFERENCE_NODE': 1, 'evaluate': 1,
+ 'execCommand': 1, 'execCommandShowHelp': 1, 'fgColor': 1,
+ 'firstChild': 1, 'forms': 1, 'getAnonymousElementByAttribute': 1,
+ 'getAnonymousNodes': 1, 'getBindingParent': 1, 'getElementById': 1,
+ 'getElementsByClassName': 1, 'getElementsByName': 1,
+ 'getElementsByTagName': 1, 'getElementsByTagNameNS': 1,
+ 'getSelection': 1, 'getUserData': 1, 'hasAttributes': 1,
+ 'hasChildNodes': 1, 'hasFocus': 1, 'head': 1, 'images': 1,
+ 'implementation': 1, 'importNode': 1, 'inputEncoding': 1,
+ 'insertBefore': 1, 'isDefaultNamespace': 1, 'isEqualNode': 1,
+ 'isSameNode': 1, 'isSupported': 1, 'lastChild': 1, 'lastModified': 1,
+ 'lastStyleSheetSet': 1, 'linkColor': 1, 'links': 1,
+ 'loadBindingDocument': 1, 'localName': 1, 'location': 1,
+ 'lookupNamespaceURI': 1, 'lookupPrefix': 1, 'mozSetImageElement': 1,
+ 'mozSyntheticDocument': 1, 'namespaceURI': 1, 'nextSibling': 1,
+ 'nodeName': 1, 'nodeType': 1, 'nodeValue': 1, 'normalize': 1,
+ 'NOTATION_NODE': 1, 'open': 1, 'ownerDocument': 1, 'parentNode': 1,
+ 'plugins': 1, 'preferredStyleSheetSet': 1, 'prefix': 1,
+ 'previousSibling': 1, 'PROCESSING_INSTRUCTION_NODE': 1,
+ 'queryCommandEnabled': 1, 'queryCommandIndeterm': 1,
+ 'queryCommandState': 1, 'queryCommandSupported': 1,
+ 'queryCommandText': 1, 'queryCommandValue': 1, 'querySelector': 1,
+ 'querySelectorAll': 1, 'readyState': 1, 'referrer': 1,
+ 'releaseCapture': 1, 'releaseEvents': 1, 'removeBinding': 1,
+ 'removeChild': 1, 'removeEventListener': 1, 'replaceChild': 1,
+ 'routeEvent': 1, 'selectedStyleSheetSet': 1, 'setUserData': 1,
+ 'styleSheets': 1, 'styleSheetSets': 1, 'TEXT_NODE': 1, 'textContent':
+ 1, 'title': 1, 'URL': 1, 'vlinkColor': 1, 'write': 1, 'writeln': 1,
+ 'xmlEncoding': 1, 'xmlStandalone': 1, 'xmlVersion': 1};
+
+var relationChecker = {
+
+ // identifies scripts across modules.
+ scriptId: null,
+
+ // stores all left-side identifier in 'assign' types.
+ assignments: null,
+
+ // stores var declarations in global scope.
+ variableDeclarations: null,
+
+ // stores top declarations in global scope.
+ functionDeclarations: null,
+
+ nonWindowProperties: null,
+
+ init: function (scriptId) {
+ this.scriptId = scriptId;
+ this.assignments = [];
+ this.variableDeclarations = {};
+ this.functionDeclarations = {};
+ this.nonWindowProperties = {};
+ },
+
+ isWindowProperty: function (identifier) {
+ return (identifier in windowPropertiesHash) ? true : false;
+ },
+
+ isDocumentProperty: function (identifier) {
+ return (identifier in documentPropertiesHash) ? true : false;
+ },
+
+ storeNodeVars: function (n) {
+ if (n.varDecls != undefined) {
+ var i = 0, le = n.varDecls.length;
+ for (; i < le; i++) {
+ this.variableDeclarations[n.varDecls[i].value] = 1;
+ }
+ }
+ },
+
+ storeNodeFunctions: function (n) {
+ if (n.funDecls != undefined) {
+ var i = 0, le = n.funDecls.length;
+ for (; i < le; i++) {
+ this.functionDeclarations[n.funDecls[i].name] = 1;
+ }
+ }
+ },
+ storeGlobalDeclarations: function (topNode) {
+ this.storeNodeVars(topNode);
+ this.storeNodeFunctions(topNode);
+ },
+ storeNodeGlobalDeclarations: function (n) {
+ if (n.global === true) {
+ this.storeNodeVars(n);
+ this.storeNodeFunctions(n);
+ }
+ },
+
+ storeNodeAssignments: function (n) {
+ if (n.type === token.ASSIGN &&
+ n.children != undefined &&
+ n.children[0].type === token.IDENTIFIER) {
+ this.assignments.push(n.children[0].value);
+ }
+ },
+
+ // checks the parent script is in global scope.
+ isInGlobalScope: function (n) {
+ var currentNode = n;
+
+ while (currentNode != undefined) {
+ if (currentNode.type === token.SCRIPT &&
+ currentNode.global === true) {
+ return true;
+ } else if (currentNode.type === token.SCRIPT) {
+ return false;
+ }
+ currentNode = currentNode.parent;
+ }
+ },
+
+ // looks for an identifier being declared as either a
+ // variable or a function within the scope. Currently,
+ // we don't care about assignments.
+ lookForIdentifierInAllScopes: function (n, val) {
+
+ var currentNode = n, i, le, vars, funcs;
+ while (currentNode != undefined) {
+ if (currentNode.varDecls != undefined) {
+ vars = currentNode.varDecls;
+ le = vars.length;
+ for (i = 0; i < le; i++) {
+ if (vars[i].value === val) {
+ console.debug('FOUND declaration for', val);
+ return true;
+ }
+ }
+ }
+ if (currentNode.funDecls != undefined) {
+ funcs = currentNode.funDecls;
+ le = funcs.length;
+ for (i = 0; i < le; i++) {
+ if (funcs[i].name === val) {
+ console.debug('FOUND function declaration for', val);
+ return true;
+ }
+ }
+ }
+ currentNode = currentNode.parent;
+ }
+ console.debug('did not find declaration or assignment for', val);
+ },
+
+ // Heuristic method for window properties.
+ // this doesn't prove they are window properties, but
+ // it allows to make a good guess. These variables could have
+ // been assigned to something else...
+ checkIdentifierIsWindowProperty: function (n) {
+
+ if (n.type === token.IDENTIFIER &&
+ (n.parent.type === token.CALL ||
+ (n.parent.type === token.DOT &&
+ n.previous != undefined &&
+ (n.previous.type === token.THIS ||
+ (n.previous.type === token.IDENTIFIER &&
+ n.previous.value === 'window')))) &&
+ n.value in windowPropertiesHash) {
+
+ this.lookForIdentifierInAllScopes(n, n.value);
+
+ }
+
+ else if (n.type === token.IDENTIFIER &&
+ n.parent != undefined &&
+ n.parent.type === token.DOT &&
+ n.previous != undefined &&
+ n.previous.type === token.THIS &&
+ this.isInGlobalScope(n)) {
+ console.debug(n.type, 'use of this in the global scope, seems ok.', n.value);
+ }
+ else if (n.type === token.IDENTIFIER) {
+ // not found.
+ console.debug(n.type, 'probably not a window prop', n.value);
+ this.nonWindowProperties[n.value] = 1;
+ }
+ }
+
+};
+
+exports.relationChecker = function (scriptId) {
+ var obj = Object.create(relationChecker);
+ obj.init(scriptId);
+ return obj;
+};