├── .gitignore
├── tests
├── js
│ ├── loadrules.js
│ └── main.js
├── mocha-includes.js
├── cases
│ ├── escapeHTML.js
│ ├── indexedDB.js
│ ├── parseFromString.js
│ ├── newFunction.js
│ ├── createContextualFragment.js
│ ├── CustomEvent.js
│ ├── eval.js
│ ├── geolocation.js
│ ├── addIdleObserver.js
│ ├── sessionStorage.js
│ ├── localStorage.js
│ ├── production_ruletests.js
│ ├── getDeviceStorage.js
│ ├── crypto.generateCRMFRequest.js
│ ├── message.js
│ ├── window.open.js
│ ├── data.js
│ ├── test_ruletests.js
│ ├── document.write.js
│ ├── action.js
│ ├── document.writeln.js
│ ├── addEventListener.js
│ ├── href.js
│ ├── src.js
│ ├── outerHTML.js
│ ├── setTimeout.js
│ ├── setInterval.js
│ ├── placeholders.js
│ ├── innerhtml.js
│ └── moz
│ │ └── moz.js
├── index.html
├── TESTING
├── advanced.html
└── css
│ └── mocha.css
├── client
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── img
│ ├── glyphicons-halflings.png
│ └── glyphicons-halflings-white.png
├── rules.readme.md
├── js
│ ├── locationctrl.js
│ ├── lib
│ │ ├── utils.js
│ │ ├── angular-route-1.2.13.min.js
│ │ ├── localforage.min.js
│ │ ├── walk.js
│ │ └── acorn_loose.js
│ ├── main.js
│ ├── scanworker.js
│ ├── rulesctrl.js
│ ├── scanservice.js
│ ├── experimentctrl.js
│ └── scanctrl.js
├── css
│ ├── angular-csp.css
│ ├── prettify.css
│ ├── dashboard.css
│ ├── app.css
│ ├── codemirror-mdn.css
│ └── codemirror.css
├── partials
│ ├── rules.html
│ ├── experiment.html
│ └── scan.html
└── index.html
├── LICENSE
├── stackato.yml
├── deploy-ghpages.sh
├── server.js
├── package.json
├── scanner.js
├── common
├── template_rules.json
└── scan.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /nbproject/
3 | .idea/
4 |
--------------------------------------------------------------------------------
/tests/js/loadrules.js:
--------------------------------------------------------------------------------
1 | ScanJS.loadRulesFile("../common/rules.json");
2 |
--------------------------------------------------------------------------------
/tests/js/main.js:
--------------------------------------------------------------------------------
1 | mocha.setup('bdd');
2 | $(document).ready(function() { mocha.run(); });
--------------------------------------------------------------------------------
/client/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/scanjs/HEAD/client/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/client/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/scanjs/HEAD/client/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/client/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/scanjs/HEAD/client/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/client/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/scanjs/HEAD/client/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/client/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/scanjs/HEAD/client/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/client/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/scanjs/HEAD/client/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This Source Code Form is subject to the terms of the Mozilla Public
2 | License, v. 2.0. If a copy of the MPL was not distributed with this
3 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
--------------------------------------------------------------------------------
/stackato.yml:
--------------------------------------------------------------------------------
1 | name: ScanJS
2 | framework:
3 | type: node
4 | runtime: node010
5 |
6 | url:
7 | - scanjs.paas.allizom.org
8 |
9 | processes:
10 | web: node server.js
11 |
--------------------------------------------------------------------------------
/client/rules.readme.md:
--------------------------------------------------------------------------------
1 | Rules maintained here:
2 | https://docs.google.com/a/mozilla.com/spreadsheets/d/1T14mvMMAspnvhKXxPNRBiV0x6QKZsXr8_b7eXJvEf_A/edit?alt=json#gid=0
3 |
4 | Converted to JSON with http://shancarter.github.io/mr-data-converter/
5 |
6 |
--------------------------------------------------------------------------------
/client/js/locationctrl.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | scanjsModule.controller('LocationCtrl', ['$scope', '$location', function LocationCtrl($scope, $location) {
4 | $scope.tabBtnClass = function (page) {
5 | var current = $location.path().substring(1) || 'scan';
6 | //console.log("l", $location.hash(), "ls", $location.hash().substring(2))
7 | return page === current ? 'active' : '';
8 | }
9 | }]);
--------------------------------------------------------------------------------
/client/css/angular-csp.css:
--------------------------------------------------------------------------------
1 | /* Include this file in your html if you are using the CSP mode. */
2 |
3 | @charset "UTF-8";
4 |
5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
6 | .ng-cloak, .x-ng-cloak,
7 | .ng-hide {
8 | display: none !important;
9 | }
10 |
11 | ng\:form {
12 | display: block;
13 | }
14 |
15 | .ng-animate-block-transitions {
16 | transition:0s all!important;
17 | -webkit-transition:0s all!important;
18 | }
19 |
--------------------------------------------------------------------------------
/client/js/lib/utils.js:
--------------------------------------------------------------------------------
1 |
2 | //Array.find polyfill
3 | if (!Array.prototype.find) {
4 | Object.defineProperty(Array.prototype, 'find', {
5 | enumerable: false,
6 | configurable: false,
7 | writable: false,
8 | value: function(predicate) {
9 | for (var i=0; i < this.length; i++) {
10 | if (predicate(this[i], i, this)) {
11 | return this[i];
12 | }
13 | }
14 | return void 0;
15 | }
16 | });
17 | }
--------------------------------------------------------------------------------
/tests/mocha-includes.js:
--------------------------------------------------------------------------------
1 | global.acorn = require("acorn");
2 | global.chai = require("chai");
3 | global.assert = chai.assert;
4 | global.should = require("chai").should();
5 | global.expect = require("chai").expect;
6 | global.AssertionError = require("chai").AssertionError;
7 |
8 | global.ScanJS = require("../common/scan.js");
9 | var fs = require("fs");
10 | var signatures = JSON.parse(fs.readFileSync("../common/rules.json", "utf8"));
11 | ScanJS.parser(acorn);
12 | ScanJS.loadRules(signatures);
13 |
--------------------------------------------------------------------------------
/deploy-ghpages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This makes sure, that we don't deploy pull requests to gh-pages :-)
4 | if [ $TRAVIS_PULL_REQUEST != false ];
5 | then
6 | echo "Not deploying test-run for a pull request"
7 | exit 0
8 | fi
9 |
10 | (
11 | git init
12 | git config user.name "Travis-CI"
13 | git config user.email "travis@nodemeatspace.com"
14 | git add -v .
15 | git commit -v -m "Deployed to Github Pages"
16 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1
17 | )
18 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var static = require("node-static");
3 | var file = new static.Server('.', {
4 | headers: {
5 | "Content-Security-Policy": "default-src 'self'; object-src 'none'; img-src 'self' data:; script-src 'self' 'unsafe-eval'",
6 | }
7 | });
8 |
9 | const PORT = process.env.PORT || 4000;
10 | require ('http').createServer(function (req, res) {
11 | req.addListener('end', function () {
12 | file.serve(req, res);
13 | }).resume();
14 | }).listen(PORT);
15 |
16 | console.log("> node-static is listening on http://127.0.0.1:"+PORT);
17 |
--------------------------------------------------------------------------------
/client/css/prettify.css:
--------------------------------------------------------------------------------
1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
--------------------------------------------------------------------------------
/client/js/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var scanjsModule = angular.module('scanjs', ['ui.bootstrap', 'ngRoute']);
4 |
5 | scanjsModule.config(['$routeProvider',
6 | function($routeProvider) {
7 | $routeProvider.
8 | when('/scan', {
9 | templateUrl: 'partials/scan.html',
10 | controller: 'ScanCtrl'
11 | }).
12 | when('/rules', {
13 | templateUrl: 'partials/rules.html',
14 | controller: 'RuleListCtrl'
15 | }).
16 | when('/experiment', {
17 | templateUrl: 'partials/experiment.html',
18 | controller: 'ExperimentCtrl'
19 | }).
20 | otherwise({
21 | redirectTo: '/scan'
22 | });
23 | }]);
--------------------------------------------------------------------------------
/tests/cases/escapeHTML.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('escapeHTML tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "escapeHTML";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var escapeHTML = "just a string";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'var nodeName = escapeHTML(node.name);';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(bad, "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | });
25 | });
26 | })();
27 |
--------------------------------------------------------------------------------
/tests/cases/indexedDB.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('indexedDB tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function() {
5 | var good = 'var a = "indexedDB.open(abase)";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function() {
12 | context(null, function() {
13 | var bad = 'var request = indexedDB.open("MyTestDatabase");';
14 | it(bad, function(){
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function() {
19 | // issue 82 - https://github.com/mozilla/scanjs/issues/82
20 | var bad = 'var a = "indexedDB"; window[a].open(3);';
21 | it.skip(bad, function(){
22 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
23 | });
24 | });
25 | });
26 | });
27 | })();
28 |
--------------------------------------------------------------------------------
/tests/cases/parseFromString.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('parseFromString tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "parseFromString";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'doc = parser.parseFromString("
somehtml
", "text/html");';
12 | it.skip(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'doc = parser.parseFromString(someVar, "text/html");';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | });
25 | });
26 | })();
27 |
--------------------------------------------------------------------------------
/tests/cases/newFunction.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('new Function() tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function () {
5 | var good = 'var Function = "static string";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function() {
12 | context(null, function () {
13 | var bad = 'new Function("alert(0)")();';
14 | it(bad, function(){
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations:true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
20 | var bad = 'var a = Function; new a("alert(0)")();';
21 | it.skip(bad, function(){
22 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations:true}), "/tests/")).not.to.be.empty;
23 | });
24 | });
25 | });
26 | });
27 | })();
28 |
--------------------------------------------------------------------------------
/tests/cases/createContextualFragment.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('createContextualFragment tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "createContextualFragment()";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var createContextualFragment = "static string";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'var documentFragment = range.createContextualFragment("bad
");';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations:true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | });
25 | });
26 | })();
27 |
--------------------------------------------------------------------------------
/client/js/scanworker.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* this makes sure that console.log can be used, even if it is undefined.
4 | We won't see the message though, since this kind of postMessage isn't handled in scanservice.js */
5 | if (typeof console === "undefined") {
6 | console = {};
7 | console.log = function consoleShim(mesg) {
8 | postMessage({'type':'log', 'message': mesg});
9 | }
10 | }
11 |
12 | importScripts('lib/acorn.js',
13 | 'lib/walk.js',
14 | 'lib/acorn_loose.js',
15 | '../../common/scan.js');
16 |
17 | //load default rules
18 | //ScanJS.loadRulesFile("../../common/rules.json")
19 |
20 | onmessage = function (evt) {
21 | if (evt.data.call === 'scan') {
22 | var args = evt.data.arguments;
23 | var source = args[0];
24 | var rules;
25 |
26 | var file = args[1];
27 |
28 | var ast = acorn.parse(source, {locations: true});
29 |
30 | var findings = ScanJS.scan(ast,file);
31 | postMessage({"filename": file, "findings": findings});
32 | }
33 | else if(evt.data.call === 'updateRules'){
34 | console.log('scanworker.js: Loaded '+evt.data.rules.length+" rules.")
35 | ScanJS.loadRules(evt.data.rules)
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/tests/cases/CustomEvent.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('CustomEvent tests - future rule?', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "CustomEvent";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'good.CustomEvent = "CustomEvent";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'obj.addEventListener("cat", function(e) { process(e.detail) }); var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}}); obj.dispatchEvent(event);';
20 | it.skip(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | });
25 | });
26 | })();
27 |
--------------------------------------------------------------------------------
/tests/cases/eval.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('eval tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function() {
5 | var good = 'var a = "eval(alert(1));";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function() {
11 | var good = 'var a = {}; a.eval = "somstring";';
12 | it(good, function(){
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function() {
18 | context(null, function() {
19 | var bad = 'eval("alert(0);");;';
20 | it(bad, function(){
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function() {
25 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
26 | var bad = 'var a = eval; a("alert(0);");';
27 | it.skip(bad, function(){
28 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
29 | });
30 | });
31 | });
32 | });
33 | })();
34 |
--------------------------------------------------------------------------------
/tests/cases/geolocation.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('geolocation tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function () {
5 | var good = 'var a = "navigator.geolocation.getCurrentPosition(success, error);";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function() {
12 | context(null, function () {
13 | var bad = 'navigator.geolocation.getCurrentPosition(showPosition);';
14 | it(bad, function(){
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | var bad = 'var a = navigator.geolocation; a.getCurrentPosition(showPosition);';
20 | it(bad, function(){
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'var a = navigator; a.geolocation.getCurrentPosition(showPosition);';
26 | it.skip(bad, function(){
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | });
31 | });
32 | })();
33 |
--------------------------------------------------------------------------------
/tests/cases/addIdleObserver.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('addIdleObserver tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "addIdleObserver()";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'addIdleObserver = "static string";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'something.addIdleObserver = "static string";';
18 | it(good, function () {
19 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
20 | });
21 | });
22 | });
23 | context('detects dangerous patterns', function () {
24 | context(null, function () {
25 | var bad = 'navigator.addIdleObserver(IdleObserver);';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations:true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | });
31 | });
32 | })();
33 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Scan.js Tests
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/cases/sessionStorage.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('sessionStorage tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "sessionStorage";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function () {
12 | context(null, function () {
13 | var bad = 'window.sessionStorage.setItem("username", "John");';
14 | it(bad, function () {
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | var bad = 'sessionStorage.setItem("username", "John");';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'var a = sessionStorage; a.setItem("username", "John");';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | });
31 | });
32 | })();
33 |
--------------------------------------------------------------------------------
/tests/cases/localStorage.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('localStorage tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function() {
5 | var good = 'var a = "localStorage.open(abase)";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function() {
11 | var good = 'var localStorage = "asdf";';
12 | it(good, function(){
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function() {
18 | context(null, function() {
19 | var bad = 'localStorage.setItem("name", "user1");';
20 | it(bad, function(){
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function() {
25 | // issue 82 - https://github.com/mozilla/scanjs/issues/82
26 | var bad = 'var a = "localStorage"; window[a].setItem("name", "user1");';
27 | it.skip(bad, function(){
28 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
29 | });
30 | });
31 | });
32 | });
33 | })();
34 |
--------------------------------------------------------------------------------
/tests/cases/production_ruletests.js:
--------------------------------------------------------------------------------
1 | describe('Testing production rules (common/rules.json)', function () {
2 | function testSetup(ruleData) {
3 |
4 | ruleData.forEach(function (rule) {
5 | describe('Rule: ' + rule.name, function () {
6 |
7 | rule.testhit.split(";").forEach(function (testsplit) {
8 | if (testsplit.trim() != "") {
9 | it(rule.source + " should match " + testsplit, function () {
10 | ScanJS.loadRules([rule]);
11 | var ast = acorn.parse(testsplit, {locations: true});
12 | var results = ScanJS.scan(ast);
13 | chai.expect(results.length).to.equal(1);
14 | });
15 | }
16 | });
17 |
18 | it(rule.name + " should not match " + rule.testmiss, function () {
19 | ScanJS.loadRules([rule]);
20 | var ast = acorn.parse(rule.testmiss, {locations: true});
21 | var results = ScanJS.scan(ast);
22 | chai.expect(results).to.have.length(0);
23 | });
24 | });
25 | });
26 | }
27 | if (typeof $ !== "undefined") {
28 | $.ajax({
29 | url: '../common/rules.json',
30 | async: false,
31 | dataType: 'json'
32 | }).done(testSetup);
33 | }
34 | else if (typeof require !== "undefined") {
35 | var fs = require("fs");
36 | var signatures = JSON.parse(fs.readFileSync("../common/rules.json", "utf8"));
37 | testSetup(signatures);
38 | }
39 | })
40 |
--------------------------------------------------------------------------------
/tests/cases/getDeviceStorage.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('getDeviceStorage tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function () {
5 | var good = 'var a = "navigator.getDeviceStorage(storageName)";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function() {
12 | context(null, function () {
13 | var bad = 'var instanceOfDeviceStorage = navigator.getDeviceStorage(storageName);';
14 | it(bad, function(){
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | var bad = 'var a = navigator; a.getDeviceStorage(storageName);';
20 | it(bad, function(){
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | // issue 82 - https://github.com/mozilla/scanjs/issues/82
26 | var bad = 'window["navigator"]["getDeviceStorage"](storageName);';
27 | it.skip(bad, function(){
28 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
29 | });
30 | });
31 | });
32 | });
33 | })();
34 |
--------------------------------------------------------------------------------
/client/js/rulesctrl.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | scanjsModule.controller('RuleListCtrl', ['$scope', 'ScanSvc', function RuleListCtrl($scope, ScanSvc) {
4 | $scope.rulesFile = "../common/rules.json";
5 | $scope.rules = []; //JSON rules object
6 |
7 | document.getElementById("rule-file-input").addEventListener("change", function (evt) {
8 | $scope.handleFileUpload(this.files);
9 | });
10 |
11 | function loadRulesFile(rulesFile) {
12 | //TODO rewrite with $http()
13 | var request = new XMLHttpRequest();
14 | request.open('GET', rulesFile);
15 |
16 | request.onload = function () {
17 | if (request.status >= 200 && request.status < 400) {
18 | $scope.rules = JSON.parse(request.responseText);
19 |
20 | ScanSvc.loadRules($scope.rules);
21 | } else {
22 | console.log('Error loading ' + rules)
23 | }
24 | $scope.$apply();
25 | };
26 |
27 | request.onerror = function () {
28 | console.log('Connection error while loading ' + rulesFile)
29 | };
30 | request.send();
31 | }
32 |
33 | $scope.handleFileUpload = function handleFileUpload(fileList) {
34 |
35 | $scope.rulesFile = fileList[0].name;
36 |
37 | var reader = new FileReader();
38 | reader.onload = function () {
39 | $scope.rules = JSON.parse(this.result);
40 | ScanSvc.loadRules($scope.rules);
41 | $scope.$apply();
42 | }
43 |
44 | reader.readAsText(fileList[0])
45 | };
46 |
47 | loadRulesFile($scope.rulesFile);
48 | }]);
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scanjs",
3 | "version": "0.0.2",
4 | "description": "Static analysis tool for javascript codebases",
5 | "main": "scanner.js",
6 | "bin": {
7 | "scanjs": "./scanner.js",
8 | "scanjs-server": "./server.js"
9 | },
10 | "preferGlobal": true,
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/mozilla/scanjs.git"
14 | },
15 | "keywords": [
16 | "scanjs",
17 | "code-analysis",
18 | "javascript",
19 | "static-analysis"
20 | ],
21 | "author": "Paul Theriault, Frederik Braun, Rob Fletcher",
22 | "license": "MPL",
23 | "dependencies": {
24 | "acorn": "~0.3.0",
25 | "tern": "~0.5.0",
26 | "optimist": "~0.5.2",
27 | "jszip": "~2.2.0",
28 | "node-static": "~0.7.3",
29 | "js-beautify":"~1.4.2"
30 | },
31 | "devDependencies": {
32 | "mocha": "*",
33 | "chai": "*"
34 | },
35 | "scripts": {
36 | "test": "cd tests && mocha -r mocha-includes cases/placeholders.js cases/test_ruletests.js cases/production_ruletests.js && mocha -r mocha-includes cases/innerhtml.js cases/CustomEvent.js cases/addIdleObserver.js cases/createContextualFragment.js cases/crypto.generateCRMFRequest.js cases/document.write.js cases/document.writeln.js cases/eval.js cases/geolocation.js cases/href.js cases/indexedDB.js cases/localStorage.js cases/message.js cases/newFunction.js cases/outerHTML.js cases/parseFromString.js cases/sessionStorage.js cases/setInterval.js cases/setTimeout.js cases/src.js cases/getDeviceStorage.js cases/moz/moz.js"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/cases/crypto.generateCRMFRequest.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('generateCRMFRequest tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'generateCRMFRequest = "static string";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var a = "generateCRMFRequest";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'crypto.generateCRMFRequest("CN=0", 0, 0, null, "console.log(1)", 384, null, "rsa-dual-use");;';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
26 | var bad = 'var a = crypto; a.generate = crypto.generateCRMFRequest; a.generate("CN=0", 0, 0, null, "console.log(1)", 384, null, "rsa-dual-use");';
27 | it.skip(bad, function () {
28 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
29 | });
30 | });
31 | });
32 | });
33 | })();
34 |
--------------------------------------------------------------------------------
/client/partials/rules.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Load new rules
8 | Load a JSON rules file to change rules:
9 |
10 |
Current rules
11 |
Number of Rules: {{rules.length}}
12 |
13 |
14 |
15 |
16 | | Rule Name |
17 | Rule Description |
18 | Rule Definition |
19 |
20 |
21 |
22 |
23 | | {{rule.name}} |
24 | {{rule.desc}} |
25 |
26 |
27 | {{rule.source}}
28 | |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/cases/message.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('message tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function () {
5 | var good = 'var a = "message";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var message = "static string";';
12 | it(good, function(){
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'function receiveMessage() { console.log(1); }';
18 | it(good, function(){
19 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
20 | });
21 | });
22 | context(null, function () {
23 | var good = 'function message() { console.log(1); }';
24 | it(good, function(){
25 | chai.expect(ScanJS.scan(acorn.parse(good, {locations:true}), "/tests/")).to.be.empty;
26 | });
27 | });
28 | });
29 | context('detects dangerous patterns', function() {
30 | context(null, function () {
31 | var bad = 'window.addEventListener("message", receiveMessage, false);';
32 | it(bad, function(){
33 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations:true}), "/tests/")).not.to.be.empty;
34 | });
35 | });
36 | });
37 | });
38 | })();
39 |
--------------------------------------------------------------------------------
/tests/cases/window.open.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('window.open tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var open = "string window.open";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var a = {}; a.open = "string"; ';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'var a = {}; a.open = function () { alert(1); }; a.open("1", "2", a);';
18 | it(good, function () {
19 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
20 | });
21 | });
22 | });
23 | context('detects dangerous patterns', function () {
24 | context(null, function () {
25 | var bad = 'win = window.open("http://www.mozilla.org", "name", fets);';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(bad, "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
32 | var bad = 'var o = window.open; o("http://www.mozilla.org", "name", {});';
33 | it.skip(bad, function () {
34 | chai.expect(ScanJS.scan(bad, "/tests/")).not.to.be.empty;
35 | });
36 | });
37 | });
38 | });
39 | })();
40 |
--------------------------------------------------------------------------------
/tests/cases/data.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('data attribute tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "something.data";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var data = "something";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'form.data = "mystring";';
18 | it(good, function () {
19 | chai.expect(ScanJS.scan(good, "/tests/")).to.be.empty;
20 | });
21 | });
22 | });
23 | context('detects dangerous patterns', function () {
24 | context(null, function () {
25 | var bad = 'var a = event.data;';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(bad, "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | var bad = 'readPipe(event.something.data.pipe, a, b);';
32 | it(bad, function () {
33 | chai.expect(ScanJS.scan(bad, "/tests/")).not.to.be.empty;
34 | });
35 | });
36 | context(null, function () {
37 | var bad = 'readPipe(event.data.pipe, a, b);';
38 | it(bad, function () {
39 | chai.expect(ScanJS.scan(bad, "/tests/")).not.to.be.empty;
40 | });
41 | });
42 | });
43 | });
44 | })();
45 |
--------------------------------------------------------------------------------
/tests/cases/test_ruletests.js:
--------------------------------------------------------------------------------
1 | describe('Testing rule templates (common/template_rules.json)', function () {
2 | function testSetup(ruleData) {
3 |
4 | ruleData.forEach(function (rule) {
5 | describe('Rule: ' + rule.name, function () {
6 | it(rule.name + " should match template " + rule.name, function () {
7 | var template = ScanJS.parseRule(rule);
8 | chai.expect(rule.name).to.equal(template);
9 | });
10 |
11 | rule.testhit.split(";").forEach(function (testsplit) {
12 | if(testsplit.trim()!=""){
13 | it(rule.source + " should match " + testsplit, function () {
14 | ScanJS.loadRules([rule]);
15 | var ast = acorn.parse(testsplit, {locations: true});
16 | var results = ScanJS.scan(ast);
17 | chai.expect(results.length).to.equal(1);
18 | });
19 | }
20 | });
21 |
22 | it(rule.name + " should not match " + rule.testmiss, function () {
23 | ScanJS.loadRules([rule]);
24 | var ast = acorn.parse(rule.testmiss, {locations: true});
25 | var results = ScanJS.scan(ast);
26 | chai.expect(results).to.have.length(0);
27 | });
28 | });
29 | });
30 | }
31 | if (typeof $ !== "undefined") {
32 | $.ajax({
33 | url: '../common/template_rules.json',
34 | async: false,
35 | dataType: 'json'
36 | }).done(testSetup);
37 | }
38 | else if (typeof require !== "undefined") {
39 | var fs = require("fs");
40 | var signatures = JSON.parse(fs.readFileSync("../common/template_rules.json", "utf8"));
41 | testSetup(signatures);
42 | }
43 | })
--------------------------------------------------------------------------------
/tests/cases/document.write.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('document.write tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'good.write = "static string";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'good = "document.write";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'document.write("Hello World!");';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'window.document.write("Hello World!");';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
32 | var bad = 'var a = window.document; a.b = document.write; a.b("bad
");';
33 | it.skip(bad, function () {
34 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
35 | });
36 | });
37 | });
38 | });
39 | })();
40 |
--------------------------------------------------------------------------------
/tests/cases/action.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('action attribute (mainly for forms) test', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var action = "static string";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function () {
12 | context(null, function () {
13 | var bad = 'var a=document.createElement("form"); a.action="demo_form.asp"; document.body.appendChild(a);';
14 | it(bad, function () {
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | // issue 73 - https://github.com/mozilla/scanjs/issues/73
20 | var bad = 'var a=document.createElement("form"); a.setAttribute("action", "demo_form.asp"); document.body.appendChild(a);';
21 | it.skip(bad, function () {
22 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
23 | });
24 | });
25 | context(null, function () {
26 | // issue 74 - https://github.com/mozilla/scanjs/issues/74
27 | var bad = 'var a=document.createElement("div"); a.innerHTML=""; document.body.appendChild(a);';
28 | it.skip(bad, function () {
29 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
30 | });
31 | });
32 | });
33 | });
34 | })();
35 |
--------------------------------------------------------------------------------
/tests/cases/document.writeln.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('document.writeln tests', function () {
3 | describe('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'good.writeln = "static string";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'good = "document.writeln";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | describe('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'document.writeln("Hello World!");';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'window.document.writeln("Hello World!");';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
32 | var bad = 'var a = window.document; a.b = document.writeln; a.b("bad
");';
33 | it.skip(bad, function () {
34 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
35 | });
36 | });
37 | });
38 | });
39 | })();
40 |
--------------------------------------------------------------------------------
/tests/cases/addEventListener.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('addEventListener tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "addEventListener";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var addEventListener = "variable with name";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | });
17 | context('detects dangerous patterns', function () {
18 | context(null, function () {
19 | var bad = 'var el = document.getElementById("outside");el.addEventListener("click", modifyText, false);' ;
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'addEventListener("click", errorPageEventHandler, true, false);';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | var bad = 'tab.linkedBrowser.addEventListener("load", function (event) {console.log(1);});';
32 | it(bad, function () {
33 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
34 | });
35 | });
36 | });
37 | });
38 | })();
39 |
--------------------------------------------------------------------------------
/client/css/dashboard.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Base structure
3 | */
4 |
5 | /* Move down content because we have a fixed navbar that is 50px tall */
6 | body {
7 | padding-top: 50px;
8 | }
9 |
10 | /*
11 | * Global add-ons
12 | */
13 |
14 | .sub-header {
15 | padding-bottom: 10px;
16 | border-bottom: 1px solid #eee;
17 | }
18 |
19 |
20 | /*
21 | * Sidebar
22 | */
23 |
24 | /* Hide for mobile, show later */
25 | .sidebar {
26 | display: none;
27 | }
28 | @media (min-width: 768px) {
29 | .sidebar {
30 | position: fixed;
31 | top: 51px;
32 | bottom: 0;
33 | left: 0;
34 | z-index: 1000;
35 | display: block;
36 | padding: 20px;
37 | overflow-x: hidden;
38 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
39 | background-color: #EAEFF2;
40 | border-right: 1px solid #eee;
41 | }
42 | }
43 |
44 | /* Sidebar navigation */
45 | .nav-sidebar {
46 | margin-right: -21px; /* 20px padding + 1px border */
47 | margin-bottom: 20px;
48 | margin-left: -20px;
49 | }
50 | .nav-sidebar > li > a {
51 | padding-right: 20px;
52 | padding-left: 20px;
53 | }
54 | .nav-sidebar > .active > a {
55 | color: #fff;
56 | background-color: #00539F;
57 | }
58 |
59 |
60 | /*
61 | * Main content
62 | */
63 |
64 | .main {
65 | padding: 20px;
66 | }
67 | @media (min-width: 768px) {
68 | .main {
69 | padding-right: 40px;
70 | padding-left: 40px;
71 | }
72 | }
73 | .main .page-header {
74 | margin-top: 0;
75 | }
76 |
77 |
78 | /*
79 | * Placeholder dashboard ideas
80 | */
81 |
82 | .placeholders {
83 | margin-bottom: 30px;
84 | text-align: center;
85 | }
86 | .placeholders h4 {
87 | margin-bottom: 0;
88 | }
89 | .placeholder {
90 | margin-bottom: 20px;
91 | }
92 | .placeholder img {
93 | display: inline-block;
94 | border-radius: 50%;
95 | }
96 |
--------------------------------------------------------------------------------
/tests/cases/href.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | describe('href tests', function() {
3 | context('ignores safe patterns', function() {
4 | context(null, function () {
5 | var good = 'var href = "static string";';
6 | it(good, function(){
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var a = document.createElement("a"); a.setAttribute("href", "http://mozilla.org"); document.body.appendChild(a);';
12 | it(good, function(){
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'var a = document.createElement("a"); a.setAttribute("href", 1); document.body.appendChild(a);';
18 | it(good, function(){
19 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
20 | });
21 | });
22 | });
23 | context('detects dangerous patterns', function() {
24 | /* deactivated, failing test.
25 | context(null, function () {
26 | var bad = 'a.href ="javascript:alert(0);";';
27 | it(bad, function(){
28 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
29 | });
30 | });
31 | */
32 | context(null, function () {
33 | // issue 73 - https://github.com/mozilla/scanjs/issues/73
34 | var bad = 'var a = document.createElement("a"); a.setAttribute("href", "javascript:alert(0)"); document.body.appendChild(a);';
35 | it.skip(bad, function(){
36 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
37 | });
38 | });
39 | });
40 | });
41 | })();
42 |
--------------------------------------------------------------------------------
/tests/TESTING:
--------------------------------------------------------------------------------
1 | TESTING
2 | ======
3 |
4 | To run tests, just use one of the following commands.
5 |
6 | Equivalent to visiting test/index.html in the browser:
7 | mocha -r mocha-includes cases/placeholders.js cases/test_ruletests.js cases/production_ruletests.js
8 |
9 | Equivalent to visiting test/advanced.html in the browser:
10 | mocha -r mocha-includes cases/innerhtml.js cases/CustomEvent.js cases/addIdleObserver.js cases/createContextualFragment.js cases/crypto.generateCRMFRequest.js cases/document.write.js cases/document.writeln.js cases/eval.js cases/geolocation.js cases/href.js cases/indexedDB.js cases/localStorage.js cases/message.js cases/newFunction.js cases/outerHTML.js cases/parseFromString.js cases/sessionStorage.js cases/setInterval.js cases/setTimeout.js cases/src.js cases/getDeviceStorage.js cases/moz/moz.js
11 |
12 | See disbaled tests in JS format below
13 |
14 | List of tests in JS format:
15 | --------------------------------------------------------------------------------
16 |
17 |
18 | var disabledTests = ["action.js",
19 | "addEventListener.js",
20 | "data.js",
21 | "escapeHTML.js",
22 | "window.open.js"]
23 |
24 |
25 | var standardTests = ["placeholders.js",
26 | "test_ruletests.js",
27 | "production_ruletests.js"]
28 |
29 | var advancedTests = ["innerhtml.js",
30 | "CustomEvent.js",
31 | "addIdleObserver.js",
32 | "createContextualFragment.js",
33 | "crypto.generateCRMFRequest.js",
34 | "document.write.js",
35 | "document.writeln.js",
36 | "eval.js",
37 | "geolocation.js",
38 | "href.js",
39 | "indexedDB.js",
40 | "localStorage.js",
41 | "message.js",
42 | "newFunction.js",
43 | "outerHTML.js",
44 | "parseFromString.js",
45 | "sessionStorage.js",
46 | "setInterval.js",
47 | "setTimeout.js",
48 | "src.js",
49 | "getDeviceStorage.js",
50 | "moz/moz.js"]
51 |
52 | var allTests = disabledTests+standardTests+advancedTests;
53 |
54 |
55 |
--------------------------------------------------------------------------------
/tests/cases/src.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('src attribute tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "something.src";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var src = "something";';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'var src = img.src;';
18 | it(good, function () {
19 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
20 | });
21 | });
22 | context(null, function () {
23 | var good = 'var a = document.createElement("script"); a.src = "static string"; document.body.appendChild(a);';
24 | it.skip(good, function () {
25 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
26 | });
27 | });
28 | });
29 | context('detects dangerous patterns', function () {
30 | /* deactivated, failing test.
31 | context(null, function () {
32 | var bad = 'obj.src = "mystring";';
33 | it(bad, function () {
34 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
35 | });
36 | });*/
37 | context(null, function () {
38 | var bad = 'var a = document.createElement("script"); a.src = variable; document.body.appendChild(a);';
39 | it(bad, function () {
40 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
41 | });
42 | });
43 | });
44 | });
45 | })();
46 |
--------------------------------------------------------------------------------
/tests/cases/outerHTML.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('outerHTML tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'good.outerHTML = "static string";';
6 | it.skip(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var a = document.createElement("div"); a.setAttribute("outerHTML", "bad
"); document.body.appendChild(a);';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'var getInnerHtml = document.getElementById("node").outerHTML;';
18 | it(good, function () {
19 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
20 | });
21 | });
22 | context(null, function () {
23 | var good = '//div.outerHTML = this is a comment';
24 | it(good, function () {
25 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
26 | });
27 | });
28 | });
29 | context('detects dangerous patterns', function () {
30 | context(null, function () {
31 | var bad = 'dangerous.outerHTML=document.location;';
32 | it(bad, function () {
33 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
34 | });
35 | });
36 | context(null, function () {
37 | var bad = 'div.outerHTML = "static string" + someVariable;';
38 | it(bad, function () {
39 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
40 | });
41 | });
42 | });
43 | });
44 | })();
45 |
--------------------------------------------------------------------------------
/tests/cases/setTimeout.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('setTimeout tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "window.setTimeout";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function () {
12 | context(null, function () {
13 | var bad = 'setTimeout("console.log(1)", 500);';
14 | it(bad, function () {
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | var bad = 'var intervalID = window.setTimeout("console.log(2)", 500);';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'something.setTimeout("console.log(3)", 500);';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
32 | var bad = 'var a = window.setTimeout; a("console.log(4)", 300);';
33 | it.skip(bad, function () {
34 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
35 | });
36 | });
37 | context(null, function () {
38 | // issue 82 - https://github.com/mozilla/scanjs/issues/82
39 | var bad = 'var a = "setTimeout"; window[a]("console.log(5)", 300);';
40 | it.skip(bad, function () {
41 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
42 | });
43 | });
44 | });
45 | });
46 | })();
47 |
--------------------------------------------------------------------------------
/tests/cases/setInterval.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('setInterval tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'var a = "window.setInterval";';
6 | it(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | });
11 | context('detects dangerous patterns', function () {
12 | context(null, function () {
13 | var bad = 'setInterval("console.log(1)", 500);';
14 | it(bad, function () {
15 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
16 | });
17 | });
18 | context(null, function () {
19 | var bad = 'var intervalID = window.setInterval("console.log(2)", 500);';
20 | it(bad, function () {
21 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
22 | });
23 | });
24 | context(null, function () {
25 | var bad = 'something.setInterval("console.log(3)", 500);';
26 | it(bad, function () {
27 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
28 | });
29 | });
30 | context(null, function () {
31 | // issue 76 - https://github.com/mozilla/scanjs/issues/76
32 | var bad = 'var a = window.setInterval; a("console.log(4)", 300);';
33 | it.skip(bad, function () {
34 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
35 | });
36 | });
37 | context(null, function () {
38 | // issue 82 - https://github.com/mozilla/scanjs/issues/82
39 | var bad = 'var a = "setInterval"; window[a]("console.log(5)", 300);';
40 | it.skip(bad, function () {
41 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
42 | });
43 | });
44 | });
45 | });
46 | })();
47 |
--------------------------------------------------------------------------------
/client/js/scanservice.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | scanjsModule.factory('ScanSvc', ['$rootScope', '$http', function($rootScope, $http) {
4 | var ScanService = {
5 | //results:[],
6 | ready:false,
7 | rules:null,
8 | init:function(rules){
9 | this.rules=rules;
10 | this.ready=true;
11 | },
12 | newScan: function(file,source) {
13 | var fileName = file || 'inline';
14 | this.scanWorker.postMessage({call: 'scan', arguments: [source, fileName]});
15 | },
16 | addResults: function(results) {
17 | $rootScope.$broadcast('NewResults', results);
18 | },
19 | loadRules:function(ruleData){
20 | this.rules=ruleData;
21 | this.scanWorker.postMessage({call: 'updateRules', rules: ruleData});
22 | }
23 | };
24 | ScanService.scanWorker = new Worker("js/scanworker.js");
25 | ScanService.scanWorker.addEventListener("message", function (evt) {
26 | if (('findings' in evt.data) && ('filename' in evt.data)) {
27 | if (evt.data.findings.length > 0) {
28 | if (evt.data.findings[0].type == 'error') {
29 | $rootScope.$broadcast('ScanError', evt.data.findings[0])
30 | return;
31 | }
32 | }
33 | ScanService.addResults(evt.data);
34 | }
35 | else if ('error' in evt.data) {
36 | // This is for errors in the worker, not in the scanning.
37 | // Exceptions (like SyntaxErrors) when scanning files
38 | // are in the findings.
39 | var exception = evt.data.error;
40 | if (e instanceof SyntaxError) {
41 | $rootScope.$broadcast('ScanError', {filename: evt.data.filename, name: exception.name, loc: exception.loc, message: exception.message })
42 | } else {
43 | throw e; // keep throwing unexpected things.
44 | }
45 | }
46 | });
47 |
48 | ScanService.scanWorker.onerror = function (e) { console.log('ScanWorker Error: ', e) };
49 |
50 | $http({method: 'GET', url: "../common/rules.json"}).
51 | success(function(data, status, headers, config) {
52 | ScanService.loadRules(data);
53 | });
54 |
55 | return ScanService;
56 | }]);
57 |
--------------------------------------------------------------------------------
/client/js/experimentctrl.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | scanjsModule.controller('ExperimentCtrl', ['$scope', 'ScanSvc', function ExperimentCtrl($scope, ScanSvc) {
4 | if (!document.getElementById("experiment-mirror").children.length) {
5 | $scope.codeMirror = new CodeMirror(document.getElementById('experiment-mirror'), {
6 | mode: 'javascript',
7 | lineNumbers: true,
8 | theme: 'mdn-like',
9 | value: "bar.foo\nfoo=something\nbaz.bar=stuff\nfoo(something)\nfoo.bar\nfoo.bar()\neval(test)\nfoo.innerHTML=danger",
10 | tabsize: 2,
11 | styleActiveLine: true
12 | });
13 | }
14 | $scope.results=[];
15 | $scope.ready=false;
16 | $scope.rule="eval()"
17 |
18 | var ruleData={
19 | "name": "manual rule",
20 | "source": $scope.rule,
21 | "testhit": $scope.rule,
22 | "testmiss": "",
23 | "desc": "Manual input.",
24 | "threat": "example"
25 | };
26 |
27 | $scope.runScan = function () {
28 | $scope.results = [];
29 | $scope.error = null;
30 | ScanJS.loadRules(ScanSvc.rules);
31 | var code = $scope.codeMirror.getValue();
32 | try {
33 | var ast = acorn.parse(code, { locations: true });
34 | $scope.results = ScanJS.scan(ast);
35 | } catch(e) {
36 | $scope.error = e;
37 | console.error(e);
38 | }
39 | $scope.lastScan = $scope.runScan;
40 | };
41 |
42 | $scope.runManualScan = function () {
43 | ruleData.source = $scope.rule;
44 | ScanJS.loadRules([ruleData]);
45 |
46 | $scope.results = [];
47 | $scope.error = null;
48 | var code = $scope.codeMirror.getValue();
49 | try {
50 | var ast = acorn.parse(code, { locations: true });
51 | $scope.results = ScanJS.scan(ast);
52 | //put ast on global variable for debugging purposes.
53 | window.ast = ast;
54 | } catch(e) {
55 | $scope.error = e;
56 | console.error(e);
57 | }
58 | $scope.lastScan = $scope.runManualScan;
59 | };
60 |
61 | $scope.showResult = function (filename,line, col) {
62 | $scope.codeMirror.setCursor(line - 1, col || 0);
63 | $scope.codeMirror.focus();
64 | };
65 |
66 | $scope.add_placeholder_char = function() {
67 | $scope.rule += '$_any';
68 | }
69 | $scope.lastScan=$scope.runScan;
70 |
71 | }]);
72 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ScanJS
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/tests/cases/placeholders.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | describe('Testing placeholders $_any, $_contains etc', function () {
5 | context('$_any', function () {
6 |
7 | it(" $_any.foo should match something.foo", function () {
8 | ScanJS.loadRules([{name:"$_any.foo",source:"$_any.foo"}]);
9 | var results = ScanJS.scan(acorn.parse("something.foo;", {locations: true}));
10 | chai.expect(results).to.have.length(1);
11 | });
12 |
13 | it(" $_any.foo should not match something.bar", function () {
14 | ScanJS.loadRules([{name:"$_any.foo",source:"$_any.foo"}]);
15 | var results = ScanJS.scan(acorn.parse("something.bar;", {locations:true}));
16 | chai.expect(results).to.have.length(0);
17 | });
18 |
19 | it(" foo.$_any should match foo.something", function () {
20 | ScanJS.loadRules([{name:"foo.$_any",source:"foo.something"}]);
21 | var results = ScanJS.scan(acorn.parse("foo.something;", {locations:true}));
22 | chai.expect(results).to.have.length(1);
23 | });
24 |
25 | it(" foo.$_any should not match bar.something", function () {
26 | ScanJS.loadRules([{name:"$_any.foo",source:"$_any.foo"}]);
27 | var results = ScanJS.scan(acorn.parse("something.bar;", {locations:true}));
28 | chai.expect(results).to.have.length(0);
29 | });
30 |
31 |
32 | });
33 |
34 | context('$_contains', function () {
35 |
36 | var rule= {name:"foo=$_unsafe",source:"foo=$_unsafe"};
37 |
38 | it(rule.source + " match template callargs", function () {
39 | var template=ScanJS.parseRule(rule);
40 | chai.expect("assignment").to.equal(template);
41 | });
42 |
43 | it(rule.source + " should match foo=identifier", function () {
44 | ScanJS.loadRules([rule]);
45 | var results = ScanJS.scan(acorn.parse("foo=identifier;", {locations:true}));
46 | chai.expect(results).to.have.length(1);
47 | });
48 |
49 | it(rule.source + " should not match bar=identifier", function () {
50 | ScanJS.loadRules([rule]);
51 | var results = ScanJS.scan(acorn.parse("bar=identifier;", {locations:true}));
52 | chai.expect(results).to.have.length(0);
53 | });
54 |
55 | it(rule.source + " should not match foo='literal'", function () {
56 | ScanJS.loadRules([rule]);
57 | var results = ScanJS.scan(acorn.parse("foo='literal;'", {locations:true}));
58 | chai.expect(results).to.have.length(0);
59 | });
60 | });
61 | })
62 |
--------------------------------------------------------------------------------
/client/css/app.css:
--------------------------------------------------------------------------------
1 | @import url("bootstrap.css");
2 | @import url("dashboard.css");
3 | @import url("codemirror.css");
4 | @import url("codemirror-mdn.css");
5 | @import url("prettify.css");
6 | @import url("font-awesome.css");
7 |
8 | html, body {
9 | height: 100%;
10 | width: 100%;
11 | }
12 |
13 | pre.prettyprint{
14 | width: auto;
15 | max-width: 600px;
16 | overflow: auto;
17 | font-size: x-small;
18 | }
19 |
20 | .navbar {
21 | background: #4D4E53;
22 | }
23 |
24 | .nav > li > a:hover,
25 | .nav > li > a:focus {
26 | background-color: #0095DD;
27 | }
28 |
29 | a {
30 | cursor:pointer;
31 | }
32 |
33 | .sidebar {
34 | background-color: #EAEFF2;
35 | }
36 |
37 | .jumbotron {
38 | background: #D4DDE4;
39 | }
40 |
41 | .table {
42 | border: 1px solid #EAEFF2;
43 | }
44 |
45 | .table-striped > tbody > tr:nth-child(odd) > td,
46 | .table-striped > tbody > tr:nth-child(odd) > th {
47 | background-color: #EAEFF2;
48 | }
49 |
50 | .sidebar-inputfiles{
51 | font-size: x-small;
52 | }
53 |
54 | .CodeMirror {
55 | border: 1px solid #EAEFF2;
56 | height:500px;
57 | }
58 |
59 | .badge {
60 | margin: 3px 3px 3px 3px;
61 | }
62 |
63 | @-webkit-keyframes rotating {
64 | 0% {
65 | -webkit-transform: rotate(0deg);
66 | color: #333;
67 | }
68 | 25% {
69 | -webkit-transform: rotate(90deg);
70 | color: #70706F;
71 | }
72 | 50% {
73 | -webkit-transform: rotate(180deg);
74 | color: #ADADAB;
75 | }
76 | 75% {
77 | -webkit-transform: rotate(270deg);
78 | color: #EAEAE7;
79 | }
80 | 100% {
81 | -webkit-transform: rotate(360deg);
82 | color: #333;
83 | }
84 | }
85 |
86 | @keyframes rotating {
87 | 0% {
88 | transform: rotate(0deg);
89 | color: #333;
90 | }
91 | 25% {
92 | transform: rotate(90deg);
93 | color: #70706F;
94 | }
95 | 50% {
96 | transform: rotate(180deg);
97 | color: #ADADAB;
98 | }
99 | 75% {
100 | transform: rotate(270deg);
101 | color: #EAEAE7;
102 | }
103 | 100% {
104 | transform: rotate(360deg);
105 | color: #333;
106 | }
107 | }
108 |
109 | .rotating {
110 | margin: 5px 3px 0 3px;
111 | -webkit-animation: rotating 1s linear infinite;
112 | animation: rotating 1s linear infinite;
113 | }
114 | .fixed {
115 | position: fixed;
116 | min-width: 45%;
117 | }
--------------------------------------------------------------------------------
/tests/cases/innerhtml.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('innerHTML tests', function () {
3 | context('ignores safe patterns', function () {
4 | context(null, function () {
5 | var good = 'good.innerHTML = "static string";';
6 | it.skip(good, function () {
7 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
8 | });
9 | });
10 | context(null, function () {
11 | var good = 'var a = document.createElement("div"); a.setAttribute("innerHTML", "bad
"); document.body.appendChild(a);';
12 | it(good, function () {
13 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
14 | });
15 | });
16 | context(null, function () {
17 | var good = 'var getInnerHtml = document.getElementById("node").innerHTML;';
18 | it(good, function () {
19 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
20 | });
21 | });
22 | context(null, function () {
23 | var good = '//div.innerHTML = this is a comment';
24 | it(good, function () {
25 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
26 | });
27 | });
28 | context(null, function () {
29 | var good = 'div.innerHTML = 1';
30 | it.skip(good, function () {
31 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
32 | });
33 | });
34 | context(null, function () {
35 | //issue 71 - https://github.com/mozilla/scanjs/issues/71
36 | var good = 'var a = 1; div.innerHTML = a';
37 | it.skip(good, function () {
38 | chai.expect(ScanJS.scan(acorn.parse(good, {locations: true}), "/tests/")).to.be.empty;
39 | });
40 | });
41 | });
42 | context('detects dangerous patterns', function () {
43 | context(null, function () {
44 | var bad = 'dangerous.innerHTML=document.location;';
45 | it(bad, function () {
46 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
47 | });
48 | });
49 | context(null, function () {
50 | var bad = 'div.innerHTML = "static string" + someVariable;';
51 | it(bad, function () {
52 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations: true}), "/tests/")).not.to.be.empty;
53 | });
54 | });
55 | });
56 | });
57 | })();
58 |
--------------------------------------------------------------------------------
/client/partials/experiment.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Results
24 |
25 |
26 |
27 |
32 | {{error.message}}
33 |
34 |
35 |
36 |
37 |
38 | | Line |
39 | Rule |
40 | Source |
41 | Description of Issue |
42 |
43 |
44 |
45 | | Line {{result.line}}
46 | | {{result.rule.name }} |
47 | {{result.rule.source }} |
48 | {{result.rule.desc}}
49 | |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/tests/advanced.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Scan.js Tests
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/scanner.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /*jshint node:true*/
3 |
4 | // This script is for using scanjs server side
5 |
6 | var fs = require('fs');
7 | var path = require('path');
8 | var beautify = require('js-beautify').js_beautify;
9 |
10 | var parser = require(__dirname+ '/client/js/lib/acorn.js');
11 | var ScanJS = require(__dirname + '/common/scan');
12 |
13 | var signatures = JSON.parse(fs.readFileSync(__dirname + "/common/rules.json", "utf8"));
14 |
15 | ScanJS.parser(parser);
16 | ScanJS.loadRules(signatures);
17 |
18 | var argv = require('optimist').usage('Usage: $node scan.js -t [path/to/app] -o [resultFile.json]').demand(['t']).argv;
19 |
20 | var dive = function(dir, action) {
21 | if( typeof action !== 'function') {
22 | action = function(error, file) {
23 | console.log(">" + file)
24 | };
25 | }
26 | list = fs.readdirSync(dir);
27 | list.forEach(function(file) {
28 | var fullpath = dir + '/' + file;
29 | try {
30 | var stat = fs.statSync(fullpath);
31 | } catch(e) {
32 | console.log("SKIPPING FILE: Could not stat " + fullpath);
33 | }
34 | if(stat && stat.isDirectory()) {
35 | dive(fullpath, action);
36 | } else {
37 | action(file, fullpath);
38 | }
39 | });
40 | };
41 | var writeReport = function(results, name) {
42 | if(fs.existsSync(name)) {
43 | console.log("Error:output file already exists (" + name + "). Supply a different name using: -o [filename]")
44 | }
45 | fs.writeFile(name, JSON.stringify(results), function(err) {
46 | if(err) {
47 | console.log(err);
48 | } else {
49 | console.log("The scan results were saved to " + name);
50 | }
51 | });
52 | };
53 |
54 |
55 | if( typeof process != 'undefined' && process.argv[2]) {
56 | results = {};
57 | reportname = argv.o ? argv.o : 'scanresults';
58 | reportdir = reportname + "_files";
59 | if(fs.existsSync(reportname) || fs.existsSync(reportdir)) {
60 | console.log("Error:output file or dir already exists (" + reportname + "). Supply a different name using: -o [filename]")
61 |
62 | }
63 | else {
64 | fs.mkdirSync(reportdir);
65 | dive(argv.t, function(file, fullpath) {
66 | var ext = path.extname(file.toString());
67 |
68 | if(ext == '.js') {
69 | var content = fs.readFileSync(fullpath, 'utf8');
70 | //beautify source so result snippet is meaningful
71 | var content = beautify(content, { indent_size: 2 });
72 |
73 | var ast = parser.parse(content, { locations: true });
74 |
75 | var scanresult = ScanJS.scan(ast, fullpath);
76 | if (scanresult.type == 'error') {
77 | console.log("SKIPPING FILE: Error in "+ fullpath+", at Line "+ scanresult.error.loc.line +", Column "+scanresult.error.loc.column+ ": " + scanresult.error.message);
78 | }
79 | results[fullpath] = scanresult;
80 | }
81 | });
82 | // Flatten report file to remove files with no findings and tests with no results (i.e. empty arr)
83 | // TODO: Don't even store empty unless --overly-verbose or so..
84 | for (var testedFile in results) {
85 | for (var testCase in results[testedFile]) {
86 | if (results[testedFile][testCase].length == 0) {
87 | delete(results[testedFile][testCase]);
88 | }
89 | }
90 | if (Object.keys(results[testedFile]).length == 0) {
91 | delete(results[testedFile]);
92 | }
93 | }
94 | writeReport(results, reportname + '.JSON');
95 | }
96 | } else {
97 | console.log('usage: $ node scan.js path/to/app ')
98 | }
99 |
--------------------------------------------------------------------------------
/client/js/lib/angular-route-1.2.13.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | AngularJS v1.2.13
3 | (c) 2010-2014 Google, Inc. http://angularjs.org
4 | License: MIT
5 | */
6 | (function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";
7 | a.$on("$routeChangeSuccess",v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,
8 | e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-
9 | 1):a+"/";k[b]=e.extend({redirectTo:a},q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=
10 | d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,
11 | {cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g
4 | Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues
5 | GitHub: @peterkroon
6 |
7 | The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation
8 |
9 | */
10 | .cm-s-mdn-like.CodeMirror { color: #999; font-family: monospace; background-color: #fff; }
11 | .cm-s-mdn-like .CodeMirror-selected { background: #cfc !important; }
12 |
13 | .cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; }
14 | .cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; margin-left: 3px; }
15 | div.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; }
16 |
17 | .cm-s-mdn-like .cm-keyword { color: #6262FF; }
18 | .cm-s-mdn-like .cm-atom { color: #F90; }
19 | .cm-s-mdn-like .cm-number { color: #ca7841; }
20 | .cm-s-mdn-like .cm-def { color: #8DA6CE; }
21 | .cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; }
22 | .cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def { color: #07a; }
23 |
24 | .cm-s-mdn-like .cm-variable { color: #07a; }
25 | .cm-s-mdn-like .cm-property { color: #905; }
26 | .cm-s-mdn-like .cm-qualifier { color: #690; }
27 |
28 | .cm-s-mdn-like .cm-operator { color: #cda869; }
29 | .cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; }
30 | .cm-s-mdn-like .cm-string { color:#07a; font-style:italic; }
31 | .cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/
32 | .cm-s-mdn-like .cm-meta { color: #000; } /*?*/
33 | .cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/
34 | .cm-s-mdn-like .cm-tag { color: #997643; }
35 | .cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/
36 | .cm-s-mdn-like .cm-header { color: #FF6400; }
37 | .cm-s-mdn-like .cm-hr { color: #AEAEAE; }
38 | .cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; }
39 | .cm-s-mdn-like .cm-error { border-bottom: 1px solid red; }
40 |
41 | div.cm-s-mdn-like .CodeMirror-activeline-background {background: #efefff;}
42 | div.cm-s-mdn-like span.CodeMirror-matchingbracket {outline:1px solid grey; color: inherit;}
43 |
44 | .cm-s-mdn-like.CodeMirror { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAyCAYAAAAp8UeFAAAHvklEQVR42s2b63bcNgyEQZCSHCdt2vd/0tWF7I+Q6XgMXiTtuvU5Pl57ZQKkKHzEAOtF5KeIJBGJ8uvL599FRFREZhFx8DeXv8trn68RuGaC8TRfo3SNp9dlDDHedyLyTUTeRWStXKPZrjtpZxaRw5hPqozRs1N8/enzIiQRWcCgy4MUA0f+XWliDhyL8Lfyvx7ei/Ae3iQFHyw7U/59pQVIMEEPEz0G7XiwdRjzSfC3UTtz9vchIntxvry5iMgfIhJoEflOz2CQr3F5h/HfeFe+GTdLaKcu9L8LTeQb/R/7GgbsfKedyNdoHsN31uRPWrfZ5wsj/NzzRQHuToIdU3ahwnsKPxXCjJITuOsi7XLc7SG/v5GdALs7wf8JjTFiB5+QvTEfRyGOfX3Lrx8wxyQi3sNq46O7QahQiCsRFgqddjBouVEHOKDgXAQHD9gJCr5sMKkEdjwsarG/ww3BMHBU7OBjXnzdyY7SfCxf5/z6ATccrwlKuwC/jhznnPF4CgVzhhVf4xp2EixcBActO75iZ8/fM9zAs2OMzKdslgXWJ9XG8PQoOAMA5fGcsvORgv0doBXyHrCwfLJAOwo71QLNkb8n2Pl6EWiR7OCibtkPaz4Kc/0NNAze2gju3zOwekALDaCFPI5vjPFmgGY5AZqyGEvH1x7QfIb8YtxMnA/b+QQ0aQDAwc6JMFg8CbQZ4qoYEEHbRwNojuK3EHwd7VALSgq+MNDKzfT58T8qdpADrgW0GmgcAS1lhzztJmkAzcPNOQbsWEALBDSlMKUG0Eq4CLAQWvEVQ9WU57gZJwZtgPO3r9oBTQ9WO8TjqXINx8R0EYpiZEUWOF3FxkbJkgU9B2f41YBrIj5ZfsQa0M5kTgiAAqM3ShXLgu8XMqcrQBvJ0CL5pnTsfMB13oB8athpAq2XOQmcGmoACCLydx7nToa23ATaSIY2ichfOdPTGxlasXMLaL0MLZAOwAKIM+y8CmicobGdCcbbK9DzN+yYGVoNNI5iUKTMyYOjPse4A8SM1MmcXgU0toOq1yO/v8FOxlASyc7TgeYaAMBJHcY1CcCwGI/TK4AmDbDyKYBBtFUkRwto8gygiQEaByFgJ00BH2M8JWwQS1nafDXQCidWyOI8AcjDCSjCLk8ngObuAm3JAHAdubAmOaK06V8MNEsKPJOhobSprwQa6gD7DclRQdqcwL4zxqgBrQcabUiBLclRDKAlWp+etPkBaNMA0AKlrHwTdEByZAA4GM+SNluSY6wAzcMNewxmgig5Ks0nkrSpBvSaQHMdKTBAnLojOdYyGpQ254602ZILPdTD1hdlggdIm74jbTp8vDwF5ZYUeLWGJpWsh6XNyXgcYwVoJQTEhhTYkxzZjiU5npU2TaB979TQehlaAVq4kaGpiPwwwLkYUuBbQwocyQTv1tA0+1UFWoJF3iv1oq+qoSk8EQdJmwHkziIF7oOZk14EGitibAdjLYYK78H5vZOhtWpoI0ATGHs0Q8OMb4Ey+2bU2UYztCtA0wFAs7TplGLRVQCcqaFdGSPCeTI1QNIC52iWNzof6Uib7xjEp07mNNoUYmVosVItHrHzRlLgBn9LFyRHaQCtVUMbtTNhoXWiTOO9k/V8BdAc1Oq0ArSQs6/5SU0hckNy9NnXqQY0PGYo5dWJ7nINaN6o958FWin27aBaWRka1r5myvLOAm0j30eBJqCxHLReVclxhxOEN2JfDWjxBtAC7MIH1fVaGdoOp4qJYDgKtKPSFNID2gSnGldrCqkFZ+5UeQXQBIRrSwocbdZYQT/2LwRahBPBXoHrB8nxaGROST62DKUbQOMMzZIC9abkuELfQzQALWTnDNAm8KHWFOJgJ5+SHIvTPcmx1xQyZRhNL5Qci689aXMEaN/uNIWkEwDAvFpOZmgsBaaGnbs1NPa1Jm32gBZAIh1pCtG7TSH4aE0y1uVY4uqoFPisGlpP2rSA5qTecWn5agK6BzSpgAyD+wFaqhnYoSZ1Vwr8CmlTQbrcO3ZaX0NAEyMbYaAlyquFoLKK3SPby9CeVUPThrSJmkCAE0CrKUQadi4DrdSlWhmah0YL9z9vClH59YGbHx1J8VZTyAjQepJjmXwAKTDQI3omc3p1U4gDUf6RfcdYfrUp5ClAi2J3Ba6UOXGo+K+bQrjjssitG2SJzshaLwMtXgRagUNpYYoVkMSBLM+9GGiJZMvduG6DRZ4qc04DMPtQQxOjEtACmhO7K1AbNbQDEggZyJwscFpAGwENhoBeUwh3bWolhe8BTYVKxQEWrSUn/uhcM5KhvUu/+eQu0Lzhi+VrK0PrZZNDQKs9cpYUuFYgMVpD4/NxenJTiMCNqdUEUf1qZWjppLT5qSkkUZbCwkbZMSuVnu80hfSkzRbQeqCZSAh6huR4VtoM2gHAlLf72smuWgE+VV7XpE25Ab2WFDgyhnSuKbs4GuGzCjR+tIoUuMFg3kgcWKLTwRqanJQ2W00hAsenfaApRC42hbCvK1SlE0HtE9BGgneJO+ELamitD1YjjOYnNYVcraGhtKkW0EqVVeDx733I2NH581k1NNxNLG0i0IJ8/NjVaOZ0tYZ2Vtr0Xv7tPV3hkWp9EFkgS/J0vosngTaSoaG06WHi+xObQkaAdlbanP8B2+2l0f90LmUAAAAASUVORK5CYII=); }
45 |
--------------------------------------------------------------------------------
/tests/css/mocha.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | body {
4 | margin:0;
5 | }
6 |
7 | #mocha {
8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | margin: 60px 50px;
10 | }
11 |
12 | #mocha ul,
13 | #mocha li {
14 | margin: 0;
15 | padding: 0;
16 | }
17 |
18 | #mocha ul {
19 | list-style: none;
20 | }
21 |
22 | #mocha h1,
23 | #mocha h2 {
24 | margin: 0;
25 | }
26 |
27 | #mocha h1 {
28 | margin-top: 15px;
29 | font-size: 1em;
30 | font-weight: 200;
31 | }
32 |
33 | #mocha h1 a {
34 | text-decoration: none;
35 | color: inherit;
36 | }
37 |
38 | #mocha h1 a:hover {
39 | text-decoration: underline;
40 | }
41 |
42 | #mocha .suite .suite h1 {
43 | margin-top: 0;
44 | font-size: .8em;
45 | }
46 |
47 | #mocha .hidden {
48 | display: none;
49 | }
50 |
51 | #mocha h2 {
52 | font-size: 12px;
53 | font-weight: normal;
54 | cursor: pointer;
55 | }
56 |
57 | #mocha .suite {
58 | margin-left: 15px;
59 | }
60 |
61 | #mocha .test {
62 | margin-left: 15px;
63 | overflow: hidden;
64 | }
65 |
66 | #mocha .test.pending:hover h2::after {
67 | content: '(pending)';
68 | font-family: arial, sans-serif;
69 | }
70 |
71 | #mocha .test.pass.medium .duration {
72 | background: #c09853;
73 | }
74 |
75 | #mocha .test.pass.slow .duration {
76 | background: #b94a48;
77 | }
78 |
79 | #mocha .test.pass::before {
80 | content: '✓';
81 | font-size: 12px;
82 | display: block;
83 | float: left;
84 | margin-right: 5px;
85 | color: #00d6b2;
86 | }
87 |
88 | #mocha .test.pass .duration {
89 | font-size: 9px;
90 | margin-left: 5px;
91 | padding: 2px 5px;
92 | color: #fff;
93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
96 | -webkit-border-radius: 5px;
97 | -moz-border-radius: 5px;
98 | -ms-border-radius: 5px;
99 | -o-border-radius: 5px;
100 | border-radius: 5px;
101 | }
102 |
103 | #mocha .test.pass.fast .duration {
104 | display: none;
105 | }
106 |
107 | #mocha .test.pending {
108 | color: #0b97c4;
109 | }
110 |
111 | #mocha .test.pending::before {
112 | content: '◦';
113 | color: #0b97c4;
114 | }
115 |
116 | #mocha .test.fail {
117 | color: #c00;
118 | }
119 |
120 | #mocha .test.fail pre {
121 | color: black;
122 | }
123 |
124 | #mocha .test.fail::before {
125 | content: '✖';
126 | font-size: 12px;
127 | display: block;
128 | float: left;
129 | margin-right: 5px;
130 | color: #c00;
131 | }
132 |
133 | #mocha .test pre.error {
134 | color: #c00;
135 | max-height: 300px;
136 | overflow: auto;
137 | }
138 |
139 | /**
140 | * (1): approximate for browsers not supporting calc
141 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
142 | * ^^ seriously
143 | */
144 | #mocha .test pre {
145 | display: block;
146 | float: left;
147 | clear: left;
148 | font: 12px/1.5 monaco, monospace;
149 | margin: 5px;
150 | padding: 15px;
151 | border: 1px solid #eee;
152 | max-width: 85%; /*(1)*/
153 | max-width: calc(100% - 42px); /*(2)*/
154 | word-wrap: break-word;
155 | border-bottom-color: #ddd;
156 | -webkit-border-radius: 3px;
157 | -webkit-box-shadow: 0 1px 3px #eee;
158 | -moz-border-radius: 3px;
159 | -moz-box-shadow: 0 1px 3px #eee;
160 | border-radius: 3px;
161 | }
162 |
163 | #mocha .test h2 {
164 | position: relative;
165 | }
166 |
167 | #mocha .test a.replay {
168 | position: absolute;
169 | top: 3px;
170 | right: 0;
171 | text-decoration: none;
172 | vertical-align: middle;
173 | display: block;
174 | width: 15px;
175 | height: 15px;
176 | line-height: 15px;
177 | text-align: center;
178 | background: #eee;
179 | font-size: 15px;
180 | -moz-border-radius: 15px;
181 | border-radius: 15px;
182 | -webkit-transition: opacity 200ms;
183 | -moz-transition: opacity 200ms;
184 | transition: opacity 200ms;
185 | opacity: 0.3;
186 | color: #888;
187 | }
188 |
189 | #mocha .test:hover a.replay {
190 | opacity: 1;
191 | }
192 |
193 | #mocha-report.pass .test.fail {
194 | display: none;
195 | }
196 |
197 | #mocha-report.fail .test.pass {
198 | display: none;
199 | }
200 |
201 | #mocha-report.pending .test.pass,
202 | #mocha-report.pending .test.fail {
203 | display: none;
204 | }
205 | #mocha-report.pending .test.pass.pending {
206 | display: block;
207 | }
208 |
209 | #mocha-error {
210 | color: #c00;
211 | font-size: 1.5em;
212 | font-weight: 100;
213 | letter-spacing: 1px;
214 | }
215 |
216 | #mocha-stats {
217 | position: fixed;
218 | top: 15px;
219 | right: 10px;
220 | font-size: 12px;
221 | margin: 0;
222 | color: #888;
223 | z-index: 1;
224 | }
225 |
226 | #mocha-stats .progress {
227 | float: right;
228 | padding-top: 0;
229 | }
230 |
231 | #mocha-stats em {
232 | color: black;
233 | }
234 |
235 | #mocha-stats a {
236 | text-decoration: none;
237 | color: inherit;
238 | }
239 |
240 | #mocha-stats a:hover {
241 | border-bottom: 1px solid #eee;
242 | }
243 |
244 | #mocha-stats li {
245 | display: inline-block;
246 | margin: 0 5px;
247 | list-style: none;
248 | padding-top: 11px;
249 | }
250 |
251 | #mocha-stats canvas {
252 | width: 40px;
253 | height: 40px;
254 | }
255 |
256 | #mocha code .comment { color: #ddd; }
257 | #mocha code .init { color: #2f6fad; }
258 | #mocha code .string { color: #5890ad; }
259 | #mocha code .keyword { color: #8a6343; }
260 | #mocha code .number { color: #2f6fad; }
261 |
262 | @media screen and (max-device-width: 480px) {
263 | #mocha {
264 | margin: 60px 0px;
265 | }
266 |
267 | #mocha #stats {
268 | position: absolute;
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/client/partials/scan.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
39 |
40 |
41 |
42 |
74 |
75 |
76 |
Results {{filteredResults.length}}
77 |
78 | Scan results will be loaded here.
79 |
80 |
99 |
Errors {{errors.length}}
100 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
File Viewer
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/client/css/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | }
8 | .CodeMirror-scroll {
9 | /* Set scrolling behaviour here */
10 | overflow: auto;
11 | }
12 |
13 | /* PADDING */
14 |
15 | .CodeMirror-lines {
16 | padding: 4px 0; /* Vertical padding around content */
17 | }
18 | .CodeMirror pre {
19 | padding: 0 4px; /* Horizontal padding of content */
20 | }
21 |
22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
23 | background-color: white; /* The little square between H and V scrollbars */
24 | }
25 |
26 | /* GUTTER */
27 |
28 | .CodeMirror-gutters {
29 | border-right: 1px solid #ddd;
30 | background-color: #f7f7f7;
31 | white-space: nowrap;
32 | }
33 | .CodeMirror-linenumbers {}
34 | .CodeMirror-linenumber {
35 | padding: 0 3px 0 5px;
36 | min-width: 20px;
37 | text-align: right;
38 | color: #999;
39 | -moz-box-sizing: content-box;
40 | box-sizing: content-box;
41 | }
42 |
43 | /* CURSOR */
44 |
45 | .CodeMirror div.CodeMirror-cursor {
46 | border-left: 1px solid black;
47 | z-index: 3;
48 | }
49 | /* Shown when moving in bi-directional text */
50 | .CodeMirror div.CodeMirror-secondarycursor {
51 | border-left: 1px solid silver;
52 | }
53 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
54 | width: auto;
55 | border: 0;
56 | background: #7e7;
57 | z-index: 1;
58 | }
59 | /* Can style cursor different in overwrite (non-insert) mode */
60 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
61 |
62 | .cm-tab { display: inline-block; }
63 |
64 | .CodeMirror-ruler {
65 | border-left: 1px solid #ccc;
66 | position: absolute;
67 | }
68 |
69 | /* DEFAULT THEME */
70 |
71 | .cm-s-default .cm-keyword {color: #708;}
72 | .cm-s-default .cm-atom {color: #219;}
73 | .cm-s-default .cm-number {color: #164;}
74 | .cm-s-default .cm-def {color: #00f;}
75 | .cm-s-default .cm-variable {color: black;}
76 | .cm-s-default .cm-variable-2 {color: #05a;}
77 | .cm-s-default .cm-variable-3 {color: #085;}
78 | .cm-s-default .cm-property {color: black;}
79 | .cm-s-default .cm-operator {color: black;}
80 | .cm-s-default .cm-comment {color: #a50;}
81 | .cm-s-default .cm-string {color: #a11;}
82 | .cm-s-default .cm-string-2 {color: #f50;}
83 | .cm-s-default .cm-meta {color: #555;}
84 | .cm-s-default .cm-qualifier {color: #555;}
85 | .cm-s-default .cm-builtin {color: #30a;}
86 | .cm-s-default .cm-bracket {color: #997;}
87 | .cm-s-default .cm-tag {color: #170;}
88 | .cm-s-default .cm-attribute {color: #00c;}
89 | .cm-s-default .cm-header {color: blue;}
90 | .cm-s-default .cm-quote {color: #090;}
91 | .cm-s-default .cm-hr {color: #999;}
92 | .cm-s-default .cm-link {color: #00c;}
93 |
94 | .cm-negative {color: #d44;}
95 | .cm-positive {color: #292;}
96 | .cm-header, .cm-strong {font-weight: bold;}
97 | .cm-em {font-style: italic;}
98 | .cm-link {text-decoration: underline;}
99 |
100 | .cm-s-default .cm-error {color: #f00;}
101 | .cm-invalidchar {color: #f00;}
102 |
103 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
104 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
105 | .CodeMirror-activeline-background {background: #e8f2ff;}
106 |
107 | /* STOP */
108 |
109 | /* The rest of this file contains styles related to the mechanics of
110 | the editor. You probably shouldn't touch them. */
111 |
112 | .CodeMirror {
113 | line-height: 1;
114 | position: relative;
115 | overflow: hidden;
116 | background: white;
117 | color: black;
118 | }
119 |
120 | .CodeMirror-scroll {
121 | /* 30px is the magic margin used to hide the element's real scrollbars */
122 | /* See overflow: hidden in .CodeMirror */
123 | margin-bottom: -30px; margin-right: -30px;
124 | padding-bottom: 30px;
125 | height: 100%;
126 | outline: none; /* Prevent dragging from highlighting the element */
127 | position: relative;
128 | -moz-box-sizing: content-box;
129 | box-sizing: content-box;
130 | }
131 | .CodeMirror-sizer {
132 | position: relative;
133 | border-right: 30px solid transparent;
134 | -moz-box-sizing: content-box;
135 | box-sizing: content-box;
136 | }
137 |
138 | /* The fake, visible scrollbars. Used to force redraw during scrolling
139 | before actuall scrolling happens, thus preventing shaking and
140 | flickering artifacts. */
141 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
142 | position: absolute;
143 | z-index: 6;
144 | display: none;
145 | }
146 | .CodeMirror-vscrollbar {
147 | right: 0; top: 0;
148 | overflow-x: hidden;
149 | overflow-y: scroll;
150 | }
151 | .CodeMirror-hscrollbar {
152 | bottom: 0; left: 0;
153 | overflow-y: hidden;
154 | overflow-x: scroll;
155 | }
156 | .CodeMirror-scrollbar-filler {
157 | right: 0; bottom: 0;
158 | }
159 | .CodeMirror-gutter-filler {
160 | left: 0; bottom: 0;
161 | }
162 |
163 | .CodeMirror-gutters {
164 | position: absolute; left: 0; top: 0;
165 | padding-bottom: 30px;
166 | z-index: 3;
167 | }
168 | .CodeMirror-gutter {
169 | white-space: normal;
170 | height: 100%;
171 | -moz-box-sizing: content-box;
172 | box-sizing: content-box;
173 | padding-bottom: 30px;
174 | margin-bottom: -32px;
175 | display: inline-block;
176 | /* Hack to make IE7 behave */
177 | *zoom:1;
178 | *display:inline;
179 | }
180 | .CodeMirror-gutter-elt {
181 | position: absolute;
182 | cursor: default;
183 | z-index: 4;
184 | }
185 |
186 | .CodeMirror-lines {
187 | cursor: text;
188 | }
189 | .CodeMirror pre {
190 | /* Reset some styles that the rest of the page might have set */
191 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
192 | border-width: 0;
193 | background: transparent;
194 | font-family: inherit;
195 | font-size: inherit;
196 | margin: 0;
197 | white-space: pre;
198 | word-wrap: normal;
199 | line-height: inherit;
200 | color: inherit;
201 | z-index: 2;
202 | position: relative;
203 | overflow: visible;
204 | }
205 | .CodeMirror-wrap pre {
206 | word-wrap: break-word;
207 | white-space: pre-wrap;
208 | word-break: normal;
209 | }
210 |
211 | .CodeMirror-linebackground {
212 | position: absolute;
213 | left: 0; right: 0; top: 0; bottom: 0;
214 | z-index: 0;
215 | }
216 |
217 | .CodeMirror-linewidget {
218 | position: relative;
219 | z-index: 2;
220 | overflow: auto;
221 | }
222 |
223 | .CodeMirror-widget {}
224 |
225 | .CodeMirror-wrap .CodeMirror-scroll {
226 | overflow-x: hidden;
227 | }
228 |
229 | .CodeMirror-measure {
230 | position: absolute;
231 | width: 100%;
232 | height: 0;
233 | overflow: hidden;
234 | visibility: hidden;
235 | }
236 | .CodeMirror-measure pre { position: static; }
237 |
238 | .CodeMirror div.CodeMirror-cursor {
239 | position: absolute;
240 | visibility: hidden;
241 | border-right: none;
242 | width: 0;
243 | }
244 | .CodeMirror-focused div.CodeMirror-cursor {
245 | visibility: visible;
246 | }
247 |
248 | .CodeMirror-selected { background: #d9d9d9; }
249 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
250 |
251 | .cm-searching {
252 | background: #ffa;
253 | background: rgba(255, 255, 0, .4);
254 | }
255 |
256 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */
257 | .CodeMirror span { *vertical-align: text-bottom; }
258 |
259 | @media print {
260 | /* Hide the cursor when printing */
261 | .CodeMirror div.CodeMirror-cursor {
262 | visibility: hidden;
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/client/js/lib/localforage.min.js:
--------------------------------------------------------------------------------
1 | !function(){var a,b,c,d;!function(){var e={},f={};a=function(a,b,c){e[a]={deps:b,callback:c}},d=c=b=function(a){function c(b){if("."!==b.charAt(0))return b;for(var c=b.split("/"),d=a.split("/").slice(0,-1),e=0,f=c.length;f>e;e++){var g=c[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(d._eak_seen=e,f[a])return f[a];if(f[a]={},!e[a])throw new Error("Could not find module "+a);for(var g,h=e[a],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)k.push("exports"===i[l]?g={}:b(c(i[l])));var n=j.apply(this,k);return f[a]=g||n}}(),a("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;ja?(b&&b(null),void c(null)):void localforage.ready().then(function(){var e=l.transaction(j,"readonly").objectStore(j),f=!1,g=e.openCursor();g.onsuccess=function(){var d=g.result;return d?void(0===a?(b&&b(d.key),c(d.key)):f?(b&&b(d.key),c(d.key)):(f=!0,d.advance(a))):(b&&b(null),void c(null))},g.onerror=function(){d(g.error.name)}})})}var h="localforage",i=1,j="keyvaluepairs",k=window.Promise,l=null,m=m||window.indexedDB||window.webkitIndexedDB||window.mozIndexedDB||window.OIndexedDB||window.msIndexedDB;if(m){var n={_driver:"asyncStorage",_initStorage:a,getItem:b,setItem:c,removeItem:d,clear:e,length:f,key:g};"function"==typeof define&&define.amd?define("asyncStorage",function(){return n}):"undefined"!=typeof module&&module.exports?module.exports=n:this.asyncStorage=n}}.call(this),function(){"use strict";function a(){return h.resolve()}function b(a){return new h(function(b){localforage.ready().then(function(){i.clear(),a&&a(),b()})})}function c(a,b){return new h(function(c,d){localforage.ready().then(function(){try{var e=i.getItem(a);e&&(e=JSON.parse(e)),b&&b(e),c(e)}catch(f){d(f)}})})}function d(a,b){return new h(function(c){localforage.ready().then(function(){var d=i.key(a);b&&b(d),c(d)})})}function e(a){return new h(function(b){localforage.ready().then(function(){var c=i.length;a&&a(c),b(c)})})}function f(a,b){return new h(function(c){localforage.ready().then(function(){i.removeItem(a),b&&b(),c()})})}function g(a,b,c){return new h(function(d,e){localforage.ready().then(function(){void 0===b&&(b=null);var f=b;try{b=JSON.stringify(b)}catch(g){e(g)}i.setItem(a,b),c&&c(f),d(f)})})}var h=window.Promise,i=null;try{i=window.localStorage}catch(j){return}var k={_driver:"localStorageWrapper",_initStorage:a,getItem:c,setItem:g,removeItem:f,clear:b,length:e,key:d};"function"==typeof define&&define.amd?define("localStorageWrapper",function(){return k}):"undefined"!=typeof module&&module.exports?module.exports=k:this.localStorageWrapper=k}.call(this),function(){"use strict";function a(){return new n(function(a){o=window.openDatabase(h,j,m,i),o.transaction(function(b){b.executeSql("CREATE TABLE IF NOT EXISTS localforage (id INTEGER PRIMARY KEY, key unique, value)",[],function(){a()},null)})})}function b(a,b){return new n(function(c,d){localforage.ready().then(function(){o.transaction(function(e){e.executeSql("SELECT * FROM localforage WHERE key = ? LIMIT 1",[a],function(a,e){var f=e.rows.length?e.rows.item(0).value:null;if(f&&f.substr(0,l)===k)try{f=JSON.parse(f.slice(l))}catch(g){d(g)}b&&b(f),c(f)},null)})})})}function c(a,b,c){return new n(function(d){localforage.ready().then(function(){void 0===b&&(b=null);var e;e="boolean"==typeof b||"number"==typeof b||"object"==typeof b?k+JSON.stringify(b):b,o.transaction(function(f){f.executeSql("INSERT OR REPLACE INTO localforage (key, value) VALUES (?, ?)",[a,e],function(){c&&c(b),d(b)},null)})})})}function d(a,b){return new n(function(c){localforage.ready().then(function(){o.transaction(function(d){d.executeSql("DELETE FROM localforage WHERE key = ?",[a],function(){b&&b(),c()},null)})})})}function e(a){return new n(function(b){localforage.ready().then(function(){o.transaction(function(c){c.executeSql("DELETE FROM localforage",[],function(){a&&a(),b()},null)})})})}function f(a){return new n(function(b){localforage.ready().then(function(){o.transaction(function(c){c.executeSql("SELECT COUNT(key) as c FROM localforage",[],function(c,d){var e=d.rows.item(0).c;a&&a(e),b(e)},null)})})})}function g(a,b){return new n(function(c){localforage.ready().then(function(){o.transaction(function(d){d.executeSql("SELECT key FROM localforage WHERE id = ? LIMIT 1",[a+1],function(a,d){var e=d.rows.length?d.rows.item(0).key:null;b&&b(e),c(e)},null)})})})}var h="localforage",i=4980736,j="1.0",k="__lfsc__:",l=k.length,m="keyvaluepairs",n=window.Promise,o=null;if(window.openDatabase){var p={_driver:"webSQLStorage",_initStorage:a,getItem:b,setItem:c,removeItem:d,clear:e,length:f,key:g};"function"==typeof define&&define.amd?define("webSQLStorage",function(){return p}):"undefined"!=typeof module&&module.exports?module.exports=p:this.webSQLStorage=p}}.call(this),function(){"use strict";var a=window.Promise,b=1,c=2,d=3,e=d;"function"==typeof define&&define.amd?e=b:"undefined"!=typeof module&&module.exports&&(e=c);var f,g=g||window.indexedDB||window.webkitIndexedDB||window.mozIndexedDB||window.OIndexedDB||window.msIndexedDB,h=this,i={INDEXEDDB:"asyncStorage",LOCALSTORAGE:"localStorageWrapper",WEBSQL:"webSQLStorage",driver:function(){return i._driver||null},_ready:a.reject(new Error("setDriver() wasn't called")),setDriver:function(d,f){return this._ready=new a(function(a,j){if(!g&&d===i.INDEXEDDB||!window.openDatabase&&d===i.WEBSQL)return f&&f(i),void j(i);if(e===b)require([d],function(b){i._extend(b),i._initStorage().then(function(){f&&f(i),a(i)})});else if(e===c){var k;switch(d){case i.INDEXEDDB:k=require("localforage/src/drivers/indexeddb");break;case i.LOCALSTORAGE:k=require("localforage/src/drivers/localstorage");break;case i.WEBSQL:k=require("localforage/src/drivers/websql")}i._extend(k),i._initStorage().then(function(){f&&f(i),a(i)})}else i._extend(h[d]),i._initStorage().then(function(){f&&f(i),a(i)})}),i._ready},ready:function(){return this._ready},_extend:function(a){for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b])}};f=g?i.INDEXEDDB:window.openDatabase?i.WEBSQL:i.LOCALSTORAGE,i.setDriver(f),e===b?define(function(){return i}):e===c?module.exports=i:this.localforage=i}.call(this);
--------------------------------------------------------------------------------
/client/js/scanctrl.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | scanjsModule.controller('ScanCtrl', ['$scope', 'ScanSvc', function ScanCtrl($scope, ScanSvc) {
4 | if (!document.getElementById("codeMirrorDiv").children.length) {
5 | $scope.codeMirror = new CodeMirror(document.getElementById('codeMirrorDiv'), {
6 | mode: 'javascript',
7 | lineNumbers: true,
8 | theme: 'mdn-like',
9 | value: "",
10 | readOnly:true,
11 | tabsize: 2,
12 | styleActiveLine: true
13 | });
14 | }
15 | $scope.codeMirrorManual = undefined;
16 | $scope.inputFiles = [];
17 | $scope.results=[];
18 | $scope.errors=[];
19 | $scope.filteredResults=[];
20 | $scope.inputFilename="";
21 | $scope.issueList=[];
22 | $scope.throbInput = false;
23 | $scope.throbOutput = false;
24 |
25 | var pending = 0;
26 | var selectedFile = 0;
27 | var codeMirror_index = 0;
28 |
29 | document.getElementById("scan-file-input").addEventListener("change", function(evt) {
30 | $scope.handleFileUpload(this.files);
31 | });
32 |
33 | $scope.run = function (source, filename) {
34 | //empty last scan
35 | $scope.results=[];
36 | $scope.errors=[];
37 | $scope.inputFiles.forEach(function (scriptFile, i) {
38 | if (document.getElementById('doScan_'+i).checked) {
39 | pending++;
40 | $scope.throbOutput = true;
41 | ScanSvc.newScan(scriptFile.name,scriptFile.asText());
42 | }
43 | });
44 |
45 | //update UI
46 | document.querySelector("#scan-input").classList.toggle("hidden",true);
47 | document.querySelector("#scan-results").classList.toggle("hidden",false);
48 | document.querySelector("#scan-output-rules").classList.toggle("hidden", false);
49 | document.querySelector("#scan-output-files").classList.toggle("hidden", false);
50 |
51 | //update navbar
52 | document.querySelector("#scan-input-nav").classList.toggle("active",false);
53 | document.querySelector("#scan-output-nav").classList.toggle("active",true);
54 | }
55 |
56 | $scope.updateIssueList = function(){
57 | $scope.issueList = $scope.results.reduce(function(p, c) {
58 | if ((c.type == 'finding') && (typeof p !== "undefined")) {
59 | if (p.indexOf(c.rule.name) < 0) {
60 | p.push(c.rule.name);
61 | }
62 | return p;
63 | }
64 | }, []);
65 | }
66 |
67 | $scope.filterResults=function(issue) {
68 | if (!issue) {
69 | $scope.filteredResults=$scope.results;
70 | }
71 | else {
72 | if (typeof issue.name != "undefined") {
73 | $scope.filteredResults = $scope.results.filter(function(result) {
74 | return result.filename === issue.name;
75 | });
76 | } else {
77 | $scope.filteredResults = $scope.results.filter(function(result) {
78 | return result.rule.name == issue;
79 | });
80 | }
81 | }
82 | }
83 |
84 | $scope.navShowInput = function () {
85 | //show input tab, hide results
86 | document.querySelector("#scan-input").classList.toggle("hidden", false);
87 | document.querySelector("#scan-results").classList.toggle("hidden", true);
88 | document.querySelector("#scan-output-rules").classList.toggle("hidden", true);
89 | document.querySelector("#scan-output-files").classList.toggle("hidden", true);
90 | //make input the active nav element
91 | document.querySelector("#scan-input-nav").classList.toggle("active", true);
92 | document.querySelector("#scan-output-nav").classList.toggle("active", false);
93 | }
94 |
95 | $scope.navShowOutput = function (filterIssue) {
96 | //show input tab, hide results
97 | document.querySelector("#scan-input").classList.toggle("hidden", true);
98 | document.querySelector("#scan-results").classList.toggle("hidden", false);
99 | document.querySelector("#scan-output-rules").classList.toggle("hidden", false);
100 | document.querySelector("#scan-output-files").classList.toggle("hidden", false);
101 | //make input the active nav element
102 | document.querySelector("#scan-input-nav").classList.toggle("active", false);
103 | document.querySelector("#scan-output-nav").classList.toggle("active", true);
104 | $scope.filterResults(filterIssue);
105 | }
106 |
107 | $scope.handleFileUpload = function handleFileUpload(fileList) {
108 | function handleMaybeZip() {
109 | //packaged app case
110 | var reader = new FileReader();
111 | $scope.inputFilename = fileList[0].name;
112 | reader.onload = function () {
113 | var magic = new DataView(this.result).getUint32(0, true /* LE */);
114 | if (magic !== 0x04034b50) { // magic marker per spec.
115 | handleList();
116 | return;
117 | }
118 | reader.onload = function() {
119 | var zip = new JSZip(this.result);
120 | $scope.inputFiles = zip.file(/\.js$/);
121 | $scope.$apply();
122 | };
123 | reader.readAsArrayBuffer(fileList[0]);
124 | };
125 | reader.readAsArrayBuffer(fileList[0].slice(0, 4));
126 | };
127 |
128 | function handleList() {
129 | //uploading individual js file(s) case
130 | $scope.inputFilename="Multiple files";
131 | var jsType = /(text\/javascript|application\/javascript|application\/x-javascript)/;
132 | var zip = new JSZip(); //create a jszip to manage the files
133 |
134 | for (var i = 0; i < fileList.length; i++) {
135 | var file = fileList[i];
136 | console.log('adding file:',file.name)
137 | if (!file.type.match(jsType)) {
138 | console.log("Ignoring non-js file:" + file.name + "(" + file.type + ")")
139 | }
140 | var reader = new FileReader();
141 | reader.readAsText(file);
142 |
143 | reader.onload = (function (file) {
144 | var fileName = file.name;
145 | return function(e){
146 | //add file to zip
147 | zip.file(fileName, e.target.result)
148 | $scope.inputFiles = zip.file(/.*/); //returns an array of files
149 | $scope.$apply();
150 |
151 | };
152 | })(file)
153 |
154 | reader.onerror = function (e) {
155 | $scope.error = "Could not read file";
156 | $scope.$apply();
157 | }
158 | }
159 | };
160 | $scope.throbInput = true;
161 | $scope.$apply();
162 | //enable fileselect div
163 | //document.querySelector("#scan-intro").classList.toggle("hidden",true);
164 | document.querySelector("#scan-files-selected").classList.toggle("hidden",false);
165 |
166 | if (fileList.length === 1) {
167 | handleMaybeZip();
168 | }
169 | else {
170 | handleList();
171 | }
172 | $scope.throbInput = false;
173 | $scope.$apply();
174 | }
175 |
176 | $scope.showFile = function (index) {
177 | document.querySelector("#code-mirror-wrapper").classList.toggle("hidden",false);
178 | if($scope.inputFiles.length<1){
179 | return;
180 | }
181 | if(!index){
182 | index=0;
183 | }
184 | if ($scope.inputFiles.length > 0) {
185 | $scope.codeMirror.setValue($scope.inputFiles[index].asText());
186 | }
187 | codeMirror_index = index;
188 | document.querySelector("#filename-badge").textContent = $scope.inputFiles[index].name;
189 | }
190 |
191 | $scope.showResult = function (filename,line, col) {
192 | document.querySelector("#code-mirror-wrapper").classList.toggle("hidden",false);
193 | document.querySelector("#filename-badge").textContent = filename;
194 | var file = $scope.inputFiles.find(function(f){return f.name === filename});
195 | $scope.codeMirror.setValue(file.asText());
196 | $scope.codeMirror.setCursor(line - 1, col || 0);
197 | $scope.codeMirror.focus();
198 | };
199 |
200 | $scope.saveState = function() {
201 | var includedAttributes = ['line','filename','rule', 'desc', 'name', 'rec','type'];
202 | /* A list of attributes we want include. Example:
203 | line: ..
204 | filename: ..
205 | rule: {
206 | desc: ..
207 | name: ..
208 | rec: ..
209 | type: ..
210 | }
211 | }
212 | */
213 | var serializedResults = JSON.stringify($scope.results, includedAttributes);
214 | localforage.setItem('results', serializedResults, function() { });
215 |
216 | var serializedErrors = JSON.stringify($scope.errors);
217 | localforage.setItem('errors', serializedErrors, function() { });
218 |
219 | var serializedInputFiles = $scope.inputFiles.map( function(el) { return {data: el.asText(), name: el.name }; });
220 | localforage.setItem("inputFiles", JSON.stringify(serializedInputFiles), function(r) { });
221 |
222 | var checkboxes = [];
223 | for (var i=0; i < $scope.inputFiles.length; i++) {
224 | checkboxes.push(document.getElementById("doScan_" + i).checked);
225 | }
226 | localforage.setItem("checkboxes", JSON.stringify(checkboxes));
227 | localforage.setItem("cm_index", JSON.stringify(codeMirror_index));
228 | };
229 |
230 | //TODO loadstate isn't called anymore, need to make it work with new workflow
231 | //TODO -> call loadState() around in main.js, line 36 (using the scanCtrlScope) and expose "reset" button in the UI.
232 | $scope.restoreState = function() {
233 | var apply = false;
234 | localforage.getItem('results', function (results_storage) {
235 | $scope.results = JSON.parse(results_storage);
236 | apply = true;
237 | });
238 | localforage.getItem('errors', function (errors_storage) {
239 | if (errors_storage) {
240 | $scope.errors = JSON.parse(errors_storage);
241 | apply = true;
242 | }
243 | });
244 | // restore files, by creating JSZip things :)
245 | localforage.getItem("inputFiles", function(inputFiles_storage) {
246 | // mimic behavior from handleFileUpload
247 | var files = JSON.parse(inputFiles_storage);
248 | var zip = new JSZip();
249 | files.forEach(function(file) {
250 | zip.file(file.name, file.data);
251 | });
252 | $scope.inputFiles = zip.file(/.*/);
253 |
254 | // nest checkbox import into the one for files, so we ensure the "inputFiles.length" check succeeds.
255 | localforage.getItem("checkboxes", function (checkboxes_storage) {
256 | var checkboxes = JSON.parse(checkboxes_storage);
257 |
258 | var ln=$scope.inputFiles.length
259 | for (var i=0; i < ln; i++) {
260 | document.getElementById("doScan_" + i).checked = checkboxes[i];
261 | }
262 | });
263 | apply = true;
264 | });
265 | if (apply) { $scope.$apply(); }
266 | };
267 |
268 | $scope.selectAll = function () {
269 | var element;
270 | var i = $scope.inputFiles.length-1;
271 | while (element=document.getElementById('doScan_'+i)) {
272 | element.checked = true;
273 | i--;
274 | }
275 | };
276 | $scope.selectNone = function () {
277 | var element;
278 | var i = $scope.inputFiles.length-1;
279 | while (element=document.getElementById('doScan_'+i)) {
280 | element.checked = false;
281 | i--;
282 | }
283 | };
284 | $scope.getSnippet = function (filename,line,numLines) {
285 | var file = $scope.inputFiles.find(function (f) {
286 | return f.name === filename
287 | });
288 | var content=file.asText();
289 | return content.split('\n').splice(line,line+numLines).join('\n');
290 | };
291 |
292 | $scope.$on('NewResults', function (event, result) {
293 | if (--pending <= 0) { $scope.throbOutput = false; }
294 | if (Object.keys(result).length === 0) {
295 | $scope.error = "Empty result set (this can also be a good thing, if you test a simple file)";
296 | return
297 | }
298 | $scope.results=$scope.results.concat(result.findings);
299 | $scope.filteredResults=$scope.results;
300 | $scope.error = "";
301 | $scope.updateIssueList();
302 | /* this is likely a bug in angular or how we use it: the HTML template sometimes does not update
303 | when we change the $scope variables without it noticing. $scope.$apply() enforces this. */
304 | $scope.$apply();
305 | $scope.saveState();
306 | });
307 |
308 | $scope.$on('ScanError', function (event, exception) {
309 | if (--pending <= 0) { $scope.throbOutput = false; }
310 | $scope.errors.push(exception);
311 | $scope.updateIssueList();
312 | $scope.$apply();
313 | $scope.saveState();
314 | });
315 |
316 | }]);
317 |
--------------------------------------------------------------------------------
/client/js/lib/walk.js:
--------------------------------------------------------------------------------
1 | // AST walker module for Mozilla Parser API compatible trees
2 |
3 | (function(mod) {
4 | if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
5 | if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
6 | mod((this.acorn || (this.acorn = {})).walk = {}); // Plain browser env
7 | })(function(exports) {
8 | "use strict";
9 |
10 | // A simple walk is one where you simply specify callbacks to be
11 | // called on specific nodes. The last two arguments are optional. A
12 | // simple use would be
13 | //
14 | // walk.simple(myTree, {
15 | // Expression: function(node) { ... }
16 | // });
17 | //
18 | // to do something with all expressions. All Parser API node types
19 | // can be used to identify node types, as well as Expression,
20 | // Statement, and ScopeBody, which denote categories of nodes.
21 | //
22 | // The base argument can be used to pass a custom (recursive)
23 | // walker, and state can be used to give this walked an initial
24 | // state.
25 | exports.simple = function(node, visitors, base, state) {
26 | if (!base) base = exports.base;
27 | function c(node, st, override) {
28 | var type = override || node.type;
29 | var found = visitors[type];
30 | base[type](node, st, c);
31 | if (found) found(node, st);
32 | }
33 | c(node, state);
34 | };
35 |
36 | // An ancestor walk builds up an array of ancestor nodes (including
37 | // the current node) and passes them to the callback as the state parameter.
38 | exports.ancestor = function(node, visitors, base, state) {
39 | if (!base) base = exports.base;
40 | if (!state) state = [];
41 | function c(node, st, override) {
42 | var type = override || node.type, found = visitors[type];
43 | if (node != st[st.length - 1]) {
44 | st = st.slice();
45 | st.push(node);
46 | }
47 | base[type](node, st, c);
48 | if (found) found(node, st);
49 | }
50 | c(node, state);
51 | };
52 |
53 | // A recursive walk is one where your functions override the default
54 | // walkers. They can modify and replace the state parameter that's
55 | // threaded through the walk, and can opt how and whether to walk
56 | // their child nodes (by calling their third argument on these
57 | // nodes).
58 | exports.recursive = function(node, state, funcs, base) {
59 | var visitor = funcs ? exports.make(funcs, base) : base;
60 | function c(node, st, override) {
61 | visitor[override || node.type](node, st, c);
62 | }
63 | c(node, state);
64 | };
65 |
66 | function makeTest(test) {
67 | if (typeof test == "string")
68 | return function(type) { return type == test; };
69 | else if (!test)
70 | return function() { return true; };
71 | else
72 | return test;
73 | }
74 |
75 | function Found(node, state) { this.node = node; this.state = state; }
76 |
77 | // Find a node with a given start, end, and type (all are optional,
78 | // null can be used as wildcard). Returns a {node, state} object, or
79 | // undefined when it doesn't find a matching node.
80 | exports.findNodeAt = function(node, start, end, test, base, state) {
81 | test = makeTest(test);
82 | try {
83 | if (!base) base = exports.base;
84 | var c = function(node, st, override) {
85 | var type = override || node.type;
86 | if ((start == null || node.start <= start) &&
87 | (end == null || node.end >= end))
88 | base[type](node, st, c);
89 | if (test(type, node) &&
90 | (start == null || node.start == start) &&
91 | (end == null || node.end == end))
92 | throw new Found(node, st);
93 | };
94 | c(node, state);
95 | } catch (e) {
96 | if (e instanceof Found) return e;
97 | throw e;
98 | }
99 | };
100 |
101 | // Find the innermost node of a given type that contains the given
102 | // position. Interface similar to findNodeAt.
103 | exports.findNodeAround = function(node, pos, test, base, state) {
104 | test = makeTest(test);
105 | try {
106 | if (!base) base = exports.base;
107 | var c = function(node, st, override) {
108 | var type = override || node.type;
109 | if (node.start > pos || node.end < pos) return;
110 | base[type](node, st, c);
111 | if (test(type, node)) throw new Found(node, st);
112 | };
113 | c(node, state);
114 | } catch (e) {
115 | if (e instanceof Found) return e;
116 | throw e;
117 | }
118 | };
119 |
120 | // Find the outermost matching node after a given position.
121 | exports.findNodeAfter = function(node, pos, test, base, state) {
122 | test = makeTest(test);
123 | try {
124 | if (!base) base = exports.base;
125 | var c = function(node, st, override) {
126 | if (node.end < pos) return;
127 | var type = override || node.type;
128 | if (node.start >= pos && test(type, node)) throw new Found(node, st);
129 | base[type](node, st, c);
130 | };
131 | c(node, state);
132 | } catch (e) {
133 | if (e instanceof Found) return e;
134 | throw e;
135 | }
136 | };
137 |
138 | // Find the outermost matching node before a given position.
139 | exports.findNodeBefore = function(node, pos, test, base, state) {
140 | test = makeTest(test);
141 | if (!base) base = exports.base;
142 | var max;
143 | var c = function(node, st, override) {
144 | if (node.start > pos) return;
145 | var type = override || node.type;
146 | if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node))
147 | max = new Found(node, st);
148 | base[type](node, st, c);
149 | };
150 | c(node, state);
151 | return max;
152 | };
153 |
154 | // Used to create a custom walker. Will fill in all missing node
155 | // type properties with the defaults.
156 | exports.make = function(funcs, base) {
157 | if (!base) base = exports.base;
158 | var visitor = {};
159 | for (var type in base) visitor[type] = base[type];
160 | for (var type in funcs) visitor[type] = funcs[type];
161 | return visitor;
162 | };
163 |
164 | function skipThrough(node, st, c) { c(node, st); }
165 | function ignore(_node, _st, _c) {}
166 |
167 | // Node walkers.
168 |
169 | var base = exports.base = {};
170 | base.Program = base.BlockStatement = function(node, st, c) {
171 | for (var i = 0; i < node.body.length; ++i)
172 | c(node.body[i], st, "Statement");
173 | };
174 | base.Statement = skipThrough;
175 | base.EmptyStatement = ignore;
176 | base.ExpressionStatement = function(node, st, c) {
177 | c(node.expression, st, "Expression");
178 | };
179 | base.IfStatement = function(node, st, c) {
180 | c(node.test, st, "Expression");
181 | c(node.consequent, st, "Statement");
182 | if (node.alternate) c(node.alternate, st, "Statement");
183 | };
184 | base.LabeledStatement = function(node, st, c) {
185 | c(node.body, st, "Statement");
186 | };
187 | base.BreakStatement = base.ContinueStatement = ignore;
188 | base.WithStatement = function(node, st, c) {
189 | c(node.object, st, "Expression");
190 | c(node.body, st, "Statement");
191 | };
192 | base.SwitchStatement = function(node, st, c) {
193 | c(node.discriminant, st, "Expression");
194 | for (var i = 0; i < node.cases.length; ++i) {
195 | var cs = node.cases[i];
196 | if (cs.test) c(cs.test, st, "Expression");
197 | for (var j = 0; j < cs.consequent.length; ++j)
198 | c(cs.consequent[j], st, "Statement");
199 | }
200 | };
201 | base.ReturnStatement = function(node, st, c) {
202 | if (node.argument) c(node.argument, st, "Expression");
203 | };
204 | base.ThrowStatement = function(node, st, c) {
205 | c(node.argument, st, "Expression");
206 | };
207 | base.TryStatement = function(node, st, c) {
208 | c(node.block, st, "Statement");
209 | if (node.handler) c(node.handler.body, st, "ScopeBody");
210 | if (node.finalizer) c(node.finalizer, st, "Statement");
211 | };
212 | base.WhileStatement = function(node, st, c) {
213 | c(node.test, st, "Expression");
214 | c(node.body, st, "Statement");
215 | };
216 | base.DoWhileStatement = base.WhileStatement;
217 | base.ForStatement = function(node, st, c) {
218 | if (node.init) c(node.init, st, "ForInit");
219 | if (node.test) c(node.test, st, "Expression");
220 | if (node.update) c(node.update, st, "Expression");
221 | c(node.body, st, "Statement");
222 | };
223 | base.ForInStatement = function(node, st, c) {
224 | c(node.left, st, "ForInit");
225 | c(node.right, st, "Expression");
226 | c(node.body, st, "Statement");
227 | };
228 | base.ForInit = function(node, st, c) {
229 | if (node.type == "VariableDeclaration") c(node, st);
230 | else c(node, st, "Expression");
231 | };
232 | base.DebuggerStatement = ignore;
233 |
234 | base.FunctionDeclaration = function(node, st, c) {
235 | c(node, st, "Function");
236 | };
237 | base.VariableDeclaration = function(node, st, c) {
238 | for (var i = 0; i < node.declarations.length; ++i) {
239 | var decl = node.declarations[i];
240 | if (decl.init) c(decl.init, st, "Expression");
241 | }
242 | };
243 |
244 | base.Function = function(node, st, c) {
245 | c(node.body, st, "ScopeBody");
246 | };
247 | base.ScopeBody = function(node, st, c) {
248 | c(node, st, "Statement");
249 | };
250 |
251 | base.Expression = skipThrough;
252 | base.ThisExpression = ignore;
253 | base.ArrayExpression = function(node, st, c) {
254 | for (var i = 0; i < node.elements.length; ++i) {
255 | var elt = node.elements[i];
256 | if (elt) c(elt, st, "Expression");
257 | }
258 | };
259 | base.ObjectExpression = function(node, st, c) {
260 | for (var i = 0; i < node.properties.length; ++i)
261 | c(node.properties[i].value, st, "Expression");
262 | };
263 | base.FunctionExpression = base.FunctionDeclaration;
264 | base.SequenceExpression = function(node, st, c) {
265 | for (var i = 0; i < node.expressions.length; ++i)
266 | c(node.expressions[i], st, "Expression");
267 | };
268 | base.UnaryExpression = base.UpdateExpression = function(node, st, c) {
269 | c(node.argument, st, "Expression");
270 | };
271 | base.BinaryExpression = base.AssignmentExpression = base.LogicalExpression = function(node, st, c) {
272 | c(node.left, st, "Expression");
273 | c(node.right, st, "Expression");
274 | };
275 | base.ConditionalExpression = function(node, st, c) {
276 | c(node.test, st, "Expression");
277 | c(node.consequent, st, "Expression");
278 | c(node.alternate, st, "Expression");
279 | };
280 | base.NewExpression = base.CallExpression = function(node, st, c) {
281 | c(node.callee, st, "Expression");
282 | if (node.arguments) for (var i = 0; i < node.arguments.length; ++i)
283 | c(node.arguments[i], st, "Expression");
284 | };
285 | base.MemberExpression = function(node, st, c) {
286 | c(node.object, st, "Expression");
287 | if (node.computed) c(node.property, st, "Expression");
288 | };
289 | base.Identifier = base.Literal = ignore;
290 |
291 | // A custom walker that keeps track of the scope chain and the
292 | // variables defined in it.
293 | function makeScope(prev, isCatch) {
294 | return {vars: Object.create(null), prev: prev, isCatch: isCatch};
295 | }
296 | function normalScope(scope) {
297 | while (scope.isCatch) scope = scope.prev;
298 | return scope;
299 | }
300 | exports.scopeVisitor = exports.make({
301 | Function: function(node, scope, c) {
302 | var inner = makeScope(scope);
303 | for (var i = 0; i < node.params.length; ++i)
304 | inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
305 | if (node.id) {
306 | var decl = node.type == "FunctionDeclaration";
307 | (decl ? normalScope(scope) : inner).vars[node.id.name] =
308 | {type: decl ? "function" : "function name", node: node.id};
309 | }
310 | c(node.body, inner, "ScopeBody");
311 | },
312 | TryStatement: function(node, scope, c) {
313 | c(node.block, scope, "Statement");
314 | if (node.handler) {
315 | var inner = makeScope(scope, true);
316 | inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param};
317 | c(node.handler.body, inner, "ScopeBody");
318 | }
319 | if (node.finalizer) c(node.finalizer, scope, "Statement");
320 | },
321 | VariableDeclaration: function(node, scope, c) {
322 | var target = normalScope(scope);
323 | for (var i = 0; i < node.declarations.length; ++i) {
324 | var decl = node.declarations[i];
325 | target.vars[decl.id.name] = {type: "var", node: decl.id};
326 | if (decl.init) c(decl.init, scope, "Expression");
327 | }
328 | }
329 | });
330 |
331 | });
--------------------------------------------------------------------------------
/common/scan.js:
--------------------------------------------------------------------------------
1 | (function (mod) {
2 |
3 | // CommonJS
4 | if (typeof exports == "object" && typeof module == "object")
5 | return mod(
6 | exports,
7 | require('../client/js/lib/walk.js')
8 | );
9 |
10 | // AMD
11 | if (typeof define == "function" && define.amd)
12 | return define([
13 | "exports",
14 | "../client/js/lib/walk.js"
15 | ], mod);
16 |
17 | // Plain browser env
18 | mod(this.ScanJS || (this.ScanJS = {}), this.acorn.walk);
19 | })(function (exports, walk) {
20 | "use strict";
21 |
22 | // Default parser, override this object to change*
23 | // needs parser.parse to produce an AST
24 | // and parser.walk the walk.js lib
25 |
26 | var parser = {};
27 | if (typeof acorn !== 'undefined' && acorn ){
28 | parser = acorn;
29 | }
30 |
31 |
32 | var rules = {};
33 | var results = [];
34 |
35 | var aw_found = function (rule, node) {
36 |
37 | results.push({
38 | type: 'finding',
39 | rule: rule,
40 | filename: results.filename,
41 | line: node.loc.start.line,
42 | col: node.loc.start.col
43 | });
44 |
45 | aw_found_callback(rule, node);
46 | };
47 | var aw_found_callback = function () {
48 | };
49 |
50 | var templateRules = {
51 | identifier: {
52 | nodeType: "Identifier",
53 | test: function (testNode, node) {
54 | if (node.type == "Identifier" && node.name == testNode.name) {
55 | return true;
56 | }
57 | }
58 | },
59 | property: {
60 | nodeType: "MemberExpression",
61 | test: function (testNode, node) {
62 | // foo.bar & foo['bar'] create different AST but mean the same thing
63 |
64 | var testName = testNode.property.type == 'Identifier' ? testNode.property.name : testNode.property.value;
65 | if (node.property && (node.property.name == testName || node.property.value == testName)) {
66 | return true;
67 | }
68 | }
69 | },
70 | object: {
71 | nodeType: "MemberExpression",
72 | test: function (testNode, node) {
73 | // foo.bar & foo['bar'] create different AST but mean the same thing
74 | if (node.object.name == testNode.object.name) {
75 | return true;
76 | }
77 | }
78 | },
79 | objectproperty: {
80 | nodeType: "MemberExpression",
81 | test: function (testNode, node) {
82 | // foo.bar & foo['bar'] create different AST but mean the same thing
83 | var testName = testNode.property.type == 'Identifier' ? testNode.property.name : testNode.property.value;
84 |
85 | if ((node.property && (node.property.name == testName || node.property.value == testName)) &&
86 | (node.object.name == testNode.object.name || // this matches the foo in foo.bar
87 | (node.object.property && node.object.property.name == testNode.object.name) )) { //this matches foo in nested objects e.g. baz.foo.bar
88 | return true;
89 | }
90 | }
91 | },
92 | new: {
93 | nodeType: "NewExpression",
94 | test: function (testNode, node) {
95 | if (node.callee.name == testNode.callee.name) {
96 | return true;
97 | }
98 | }
99 | },
100 | call: {
101 | nodeType: "CallExpression",
102 | test: function (testNode, node) {
103 | // matches foo()
104 | if (node.callee.type == 'Identifier' &&
105 | node.callee.name == testNode.callee.name) {
106 | return true;
107 | }
108 | }
109 | },
110 | propertycall: {
111 | nodeType: "CallExpression",
112 | test: function (testNode, node) {
113 | if (templateRules.property.test(testNode.callee, node.callee)) {
114 | return true;
115 | }
116 | }
117 | },
118 | objectpropertycall: {
119 | nodeType: "CallExpression",
120 | test: function (testNode, node) {
121 | if (templateRules.objectproperty.test(testNode.callee, node.callee)) {
122 | return true;
123 | }
124 | }
125 | },
126 | matchArgs: function (testNode, node) {
127 | var matching = node.arguments.length > 0;
128 | var index = 0;
129 | while (matching && index < testNode.arguments.length) {
130 | var testArg = testNode.arguments[index];
131 | //ensure each literal argument matches
132 | if (testArg.type == "Literal") {
133 | if (typeof node.arguments[index] == 'undefined' || node.arguments[index].type != "Literal" || testArg.value != node.arguments[index].value) {
134 | matching = false;
135 | }
136 | }
137 | index++;
138 | }
139 | if (matching) {
140 | return true;
141 | }
142 | },
143 | callargs: {
144 | nodeType: "CallExpression",
145 | test: function (testNode, node) {
146 | if (templateRules.call.test(testNode, node) &&
147 | templateRules.matchArgs(testNode, node)) {
148 | return true;
149 | }
150 | }
151 | },
152 | propertycallargs: {
153 | nodeType: "CallExpression",
154 | test: function (testNode, node) {
155 | if (templateRules.propertycall.test(testNode, node) &&
156 | templateRules.matchArgs(testNode, node)) {
157 | return true;
158 | }
159 | }
160 | },
161 | objectpropertycallargs: {
162 | nodeType: "CallExpression",
163 | test: function (testNode, node) {
164 | if (templateRules.objectpropertycall.test(testNode, node) &&
165 | templateRules.matchArgs(testNode, node)) {
166 | return true;
167 | }
168 | }
169 | },
170 | assignment: {
171 | nodeType: "AssignmentExpression",
172 | test: function (testNode, node) {
173 | if (templateRules.identifier.test(testNode.left, node.left)) {
174 | //support $_unsafe for RHS of assignment
175 | var unsafe = true;
176 | if (testNode.right.type == "Identifier" && testNode.right.name == "$_unsafe") {
177 | unsafe = templateRules.$_contains(node.right, "Identifier")
178 | }
179 | return unsafe;
180 | }
181 | }
182 | },
183 | propertyassignment: {
184 | nodeType: "AssignmentExpression",
185 | test: function (testNode, node) {
186 | //support $_unsafe for RHS of assignment
187 | var unsafe = true;
188 | if (testNode.right.type == "Identifier" && testNode.right.name == "$_unsafe") {
189 | unsafe = templateRules.$_contains(node.right, "Identifier")
190 | }
191 |
192 | if (templateRules.property.test(testNode.left, node.left) && unsafe) {
193 | return true;
194 | }
195 | }
196 | },
197 | objectpropertyassignment: {
198 | nodeType: "AssignmentExpression",
199 | test: function (testNode, node) {
200 |
201 | //support $_unsafe for RHS of assignment
202 | var unsafe = true;
203 | if (testNode.right.type == "Identifier" && testNode.right.name == "$_unsafe") {
204 | unsafe = templateRules.$_contains(node.right, "Identifier")
205 | }
206 |
207 | if (templateRules.objectproperty.test(testNode.left, node.left) && unsafe) {
208 | return true;
209 | }
210 | }
211 | },
212 | $_contains: function (node, typestring) {
213 | var foundnode = walk.findNodeAt(node, null, null, typestring);
214 | return typeof foundnode != 'undefined'
215 | },
216 | ifstatement: {
217 | nodeType: "IfStatement",
218 | test: function (testNode, node) {
219 | if (testNode.test.type == "CallExpression" && testNode.test.callee.name == "$_contains") {
220 | if (testNode.test.arguments[0].type == "Literal") {
221 | if (templateRules.$_contains(node.test, testNode.test.arguments[0].value)) {
222 | return true;
223 | }
224 | }
225 | }
226 | }
227 | }
228 | };
229 |
230 | function aw_loadRulesFile(rulesFile, callback) {
231 |
232 | var request = new XMLHttpRequest();
233 |
234 | request.open('GET', rulesFile, false);
235 |
236 | request.onload = function () {
237 | if (request.status >= 200 && request.status < 400) {
238 | rulesData = JSON.parse(request.responseText);
239 | aw_loadRules(rulesData);
240 | if (typeof callback == 'function')
241 | callback(rules);
242 | } else {
243 | console.log('Error loading ' + rules)
244 | }
245 | };
246 |
247 | request.onerror = function () {
248 | console.log('Connection error while loading ' + rulesFile)
249 | };
250 | request.send();
251 | }
252 |
253 |
254 | function aw_parseRule(rule) {
255 | try {
256 | var program = parser.parse(rule.source);
257 | //each rule must contain exactly one javascript statement
258 | if (program.body.length != 1) {
259 | throw ('Rule ' + rule.name + 'contains too many statements, skipping: ' + rule.source);
260 |
261 | }
262 | rule.statement = program.body[0]
263 | } catch (e) {
264 | throw('Can\'t parse rule:' + rule.name );
265 | }
266 |
267 | if (rule.statement.type == "IfStatement") {
268 | return 'ifstatement';
269 | }
270 |
271 | //identifier
272 | if (rule.statement.expression.type == "Identifier") {
273 | return 'identifier';
274 | }
275 |
276 | if (rule.statement.expression.type == "NewExpression") {
277 | return 'new';
278 | }
279 |
280 | //property, objectproperty
281 | if (rule.statement.expression.type == "MemberExpression") {
282 | if (rule.statement.expression.object.name == "$_any") {
283 | //rule is $_any.foo, this is a property rule
284 | return 'property';
285 | } else if (rule.statement.expression.property.name == "$_any") {
286 | return 'object';
287 | }
288 | else {
289 | return 'objectproperty';
290 | }
291 | }
292 | //call, propertycall,objectpropertycall ( + args variants)
293 | if (rule.statement.expression.type == "CallExpression") {
294 | var args = '';
295 | if (rule.statement.expression.arguments.length > 0) {
296 | args = 'args';
297 | }
298 |
299 | if (rule.statement.expression.callee.type == "Identifier") {
300 | return 'call' + args;
301 | } else if (rule.statement.expression.callee.type == "MemberExpression") {
302 | if (rule.statement.expression.callee.object.name == "$_any") {
303 | return 'propertycall' + args;
304 | } else {
305 | return 'objectpropertycall' + args;
306 | }
307 | }
308 | }
309 |
310 | //assignment, propertyassignment, objectpropertyassignment
311 | if (rule.statement.expression.type == "AssignmentExpression") {
312 | if (rule.statement.expression.left.type == "MemberExpression") {
313 | if (rule.statement.expression.left.object.name == "$_any") {
314 | return 'propertyassignment';
315 | } else {
316 | return 'objectpropertyassignment';
317 | }
318 | } else {
319 | return 'assignment';
320 | }
321 | }
322 |
323 |
324 | //if we get to here we couldn't find a matching template for the rule.source
325 | throw ("No matching template")
326 | }
327 |
328 | function aw_loadRules(rulesData) {
329 |
330 | var nodeTests = {};
331 | //each node type may have multiple tests, so first create arrays of test funcs
332 | for (var i in rulesData) {
333 | var rule = rulesData[i];
334 | //parse rule source
335 | var template;
336 | try {
337 | template = templateRules[aw_parseRule(rule)];
338 | } catch (e) {
339 | console.log("Error parsing rule " + rule.name, rule.source)
340 | break;
341 | }
342 |
343 | if (typeof template == 'undefined') {
344 | console.log("Should never get here.")
345 | break;
346 | }
347 |
348 | if (!nodeTests[template.nodeType]) {
349 | nodeTests[template.nodeType] = [];
350 | }
351 | nodeTests[template.nodeType].push(function (template, rule, node) {
352 | if (rule.statement.expression) {
353 | if (template.test(rule.statement.expression, node)) {
354 | aw_found(rule, node);
355 | }
356 | }
357 | else {
358 | if (template.test(rule.statement, node)) {
359 | aw_found(rule, node);
360 | }
361 | }
362 | }.bind(undefined, template, rule));
363 | }
364 |
365 |
366 | rules = {};
367 | //create a single function for each nodeType, which calls all the test functions
368 | for (var nodeType in nodeTests) {
369 | rules[nodeType] = function (tests, node) {
370 | tests.forEach(function (test) {
371 | test.call(this, node);
372 | });
373 | }.bind(undefined, nodeTests[nodeType]);
374 | }
375 | }
376 |
377 | function aw_scan(ast, filename) {
378 | results = [];
379 | results.filename = "Manual input"
380 |
381 | if (typeof filename != 'undefined') {
382 | results.filename = filename;
383 | }
384 | if (!rules) {
385 | return [
386 | {
387 | type: 'error',
388 | name: 'RulesError',
389 | pos: 0,
390 | loc: { column: 0, line: 0 },
391 | message: "Could not scan with no rules loaded.",
392 | filename: filename
393 | }
394 | ];
395 | }
396 | walk.simple(ast, rules);
397 |
398 | return results;
399 | }
400 |
401 | function aw_setCallback(found_callback) {
402 | aw_found_callback = found_callback;
403 | }
404 |
405 | function aw_setParser(newParser){
406 | parser = newParser;
407 | }
408 |
409 | exports.rules = rules;
410 | exports.scan = aw_scan;
411 | exports.loadRulesFile = aw_loadRulesFile;
412 | exports.loadRules = aw_loadRules;
413 | exports.parseRule = aw_parseRule;
414 | exports.setResultCallback = aw_setCallback;
415 | exports.parser = aw_setParser;
416 |
417 | });
418 |
--------------------------------------------------------------------------------
/tests/cases/moz/moz.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | describe('Mozilla specific tests', function () {
3 | context('MozActivity', function () {
4 | context('safe', function () {
5 | context(null, function () {
6 | var good = 'var a = "static MozActivity";';
7 | it(good, function () {
8 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
9 | });
10 | });
11 | context(null, function () {
12 | var good = 'var MozActivity = "static MozActivity";';
13 | it(good, function () {
14 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
15 | });
16 | });
17 | });
18 | context('dangerous', function () {
19 | context(null, function () {
20 | var bad = 'var activity = new MozActivity({ name: "pick",data: {type: "image/jpeg"}});';
21 | it(bad, function () {
22 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
23 | });
24 | });
25 | });
26 | });
27 |
28 | context('.mozAlarms', function () {
29 | context('safe', function () {
30 | context(null, function () {
31 | var good = 'var mozAlarms = "just a string, not .mozAlarms";';
32 | it(good, function () {
33 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
34 | });
35 | });
36 | });
37 | context('dangerous', function () {
38 | context(null, function () {
39 | var bad = 'var bad = window.navigator.mozAlarms;';
40 | it(bad, function () {
41 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
42 | });
43 | });
44 | });
45 | });
46 |
47 | context('mozApps.mgmt', function () {
48 | context('safe', function () {
49 | context(null, function () {
50 | var good = 'var getAll = "mozApps.mgmt.getAll()";';
51 | it(good, function () {
52 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
53 | });
54 | });
55 | });
56 | context('dangerous', function () {
57 | context(null, function () {
58 | var bad = 'var stuffs = navigator.mozApps.mgmt;';
59 | it(bad, function () {
60 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
61 | });
62 | });
63 | context(null, function () {
64 | var bad = 'var stuffs = navigator.mozApps.mgmt.getAll()';
65 | it(bad, function () {
66 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
67 | });
68 | });
69 | });
70 | });
71 |
72 | context('.mozBluetooth', function () {
73 | context('safe', function () {
74 | context(null, function () {
75 | var good = 'var mozBluetooth = "just a string, not .mozBluetooth";';
76 | it(good, function () {
77 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
78 | });
79 | });
80 | });
81 | context('dangerous', function () {
82 | context(null, function () {
83 | var bad = 'var bad = window.navigator.mozBluetooth;';
84 | it(bad, function () {
85 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
86 | });
87 | });
88 | });
89 | });
90 |
91 | context('mozCameras', function () {
92 | context('safe', function () {
93 | context(null, function () {
94 | var good = 'var mozCameras = "just a string, not mozCameras";';
95 | it(good, function () {
96 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
97 | });
98 | });
99 | });
100 | context('dangerous', function () {
101 | context(null, function () {
102 | var bad = 'var bad = window.navigator.mozCameras;';
103 | it(bad, function () {
104 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
105 | });
106 | });
107 | });
108 | });
109 |
110 |
111 | context('dangerous', function () {
112 | context(null, function () {
113 | var bad = 'var bad = window.navigator.mozCellBroadcast;';
114 | it(bad, function () {
115 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
116 | });
117 | });
118 | });
119 | });
120 |
121 | context('mozChromeEvent', function () {
122 | context('safe', function () {
123 | context(null, function () {
124 | var good = 'var a = "string mozChromeEvent";';
125 | it(good, function () {
126 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
127 | });
128 | });
129 | context(null, function () {
130 | var good = 'var mozChromeEvent = "string mozChromeEvent";';
131 | it(good, function () {
132 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
133 | });
134 | });
135 | });
136 | context('dangerous', function () {
137 | context(null, function () {
138 | var bad = 'window.addEventListener("mozChromeEvent", function (e) {console.log("mozilla rocks!") });';
139 | it(bad, function () {
140 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
141 | });
142 | });
143 | });
144 | });
145 |
146 | context('.mozContacts', function () {
147 | context('safe', function () {
148 | context(null, function () {
149 | var good = 'var mozContacts = "just a string, not .mozContacts";';
150 | it(good, function () {
151 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
152 | });
153 | });
154 | });
155 | context('dangerous', function () {
156 | context(null, function () {
157 | var bad = 'var bad = window.navigator.mozContacts;';
158 | it(bad, function () {
159 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
160 | });
161 | });
162 | });
163 | });
164 |
165 | context('mozFMRadio', function () {
166 | context('safe', function () {
167 | context(null, function () {
168 | var good = 'var a = "string mozFMRadio;";';
169 | it(good, function () {
170 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
171 | });
172 | });
173 | });
174 | context('dangerous', function () {
175 | context(null, function () {
176 | var bad = 'var WebFM = navigator.mozFMRadio;';
177 | it(bad, function () {
178 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
179 | });
180 | });
181 | });
182 | });
183 |
184 | context('mozKeyboard', function () {
185 | context('safe', function () {
186 | context(null, function () {
187 | var good = 'var a = "mozKeyboard";';
188 | it(good, function () {
189 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
190 | });
191 | });
192 | });
193 | context('dangerous', function () {
194 | /* deactivated, failing test.
195 | context(null, function () {
196 | var bad = 'mozKeyboard.onfocuschange = alert(0);';
197 | it(bad, function () {
198 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
199 | });
200 | });
201 | */
202 | });
203 | });
204 |
205 | context('mozMobileConnection', function () {
206 | context('safe', function () {
207 | context(null, function () {
208 | var good = 'var a = "mozMobileConnection";';
209 | it(good, function () {
210 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
211 | });
212 | });
213 | });
214 | context('dangerous', function () {
215 | /* deactivated, failing test.
216 | context(null, function () {
217 | var bad = 'MozMobileConnection.sendMMI()';
218 | it(bad, function () {
219 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
220 | });
221 | });
222 | */
223 | });
224 | });
225 |
226 | context('.mozNotification', function () {
227 | context('safe', function () {
228 | context(null, function () {
229 | var good = 'var mozNotification = ".mozNotification, this is just a string";';
230 | it(good, function () {
231 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
232 | });
233 | });
234 | });
235 | context('dangerous', function () {
236 | context(null, function () {
237 | var bad = "if (window.webkitNotifications) { _popup = window; } else if (navigator.mozNotification) { console.log (1); }";
238 | it(bad, function () {
239 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
240 | });
241 | });
242 | });
243 | });
244 |
245 | context('mozPermissionSettings', function () {
246 | context('safe', function () {
247 | context(null, function () {
248 | var good = 'var a = "just a string, not mozPermissionSettings";';
249 | it(good, function () {
250 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
251 | });
252 | });
253 | context(null, function () {
254 | var good = 'var mozPermissionSettings = "just a string, not mozPermissionSettings";';
255 | it(good, function () {
256 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
257 | });
258 | });
259 | });
260 | context('dangerous', function () {
261 | context(null, function () {
262 | var bad = 'var permissions = window.navigator.mozPermissionSettings;';
263 | it(bad, function () {
264 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
265 | });
266 | });
267 | });
268 | });
269 |
270 | context('mozPower', function () {
271 | context('safe', function () {
272 | context(null, function () {
273 | var good = 'var a = "just a string, window.navigator.mozPower;";';
274 | it(good, function () {
275 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
276 | });
277 | });
278 | context(null, function () {
279 | var good = 'var mozPower = "just a string, window.navigator.mozPower;";';
280 | it(good, function () {
281 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
282 | });
283 | });
284 | });
285 | context('dangerous', function () {
286 | context(null, function () {
287 | var bad = 'var power = window.navigator.mozPower;';
288 | it(bad, function () {
289 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
290 | });
291 | });
292 | });
293 | });
294 |
295 | context('mozSetMessageHandler', function () {
296 | context('safe', function () {
297 | context(null, function () {
298 | var good = 'var a = "static mozSetMessageHandler";';
299 | it(good, function () {
300 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
301 | });
302 | });
303 | });
304 | context('dangerous', function () {
305 | context(null, function () {
306 | var bad = 'navigator.mozSetMessageHandler(type, handler);';
307 | it(bad, function () {
308 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
309 | });
310 | });
311 | });
312 | });
313 |
314 | context('mozSettings', function () {
315 | context('safe', function () {
316 | context(null, function () {
317 | var good = 'var a = "window.navigator.mozSettings;";';
318 | it(good, function () {
319 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
320 | });
321 | });
322 | context(null, function () {
323 | var good = 'var mozSettings = "window.navigator.mozSettings;";';
324 | it(good, function () {
325 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
326 | });
327 | });
328 | });
329 | context('dangerous', function () {
330 | context(null, function () {
331 | var bad = 'var settings = window.navigator.mozSettings;';
332 | it(bad, function () {
333 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
334 | });
335 | });
336 | });
337 | });
338 |
339 | context('mozSms', function () {
340 | context('safe', function () {
341 | context(null, function () {
342 | var good = 'var a = "window.navigator.mozSms;";';
343 | it(good, function () {
344 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
345 | });
346 | });
347 | context(null, function () {
348 | var good = 'var mozSms = "window.navigator.mozSms;";';
349 | it(good, function () {
350 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
351 | });
352 | });
353 | });
354 | context('dangerous', function () {
355 | /* deactivated, failing test.
356 | context(null, function () {
357 | var bad = 'var sms = window.navigator.mozSms;';
358 | it(bad, function () {
359 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
360 | });
361 | });
362 | */
363 | /* deactivated, failing test.
364 | context(null, function () {
365 | var bad = 'var msg = window.navigator.mozSms.getMessage(1);';
366 | it(bad, function () {
367 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
368 | });
369 | });
370 | */
371 | });
372 | });
373 |
374 | context('mozSystem', function () {
375 | context('safe', function () {
376 | context(null, function () {
377 | var good = 'var a = "mozSystem: true";';
378 | it(good, function () {
379 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
380 | });
381 | });
382 | });
383 | context('dangerous', function () {
384 | context(null, function () {
385 | var bad = 'var xhr = new XMLHttpRequest({ mozSystem: true});';
386 | it(bad, function () {
387 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
388 | });
389 | });
390 | });
391 | });
392 |
393 | context('.mozTCPSocket', function () {
394 | context('safe', function () {
395 | context(null, function () {
396 | var good = 'var a = "navigator.mozTCPSocket;"';
397 | it(good, function () {
398 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
399 | });
400 | });
401 | });
402 | context('dangerous', function () {
403 | context(null, function () {
404 | var bad = 'var socket = navigator.mozTCPSocket;';
405 | it(bad, function () {
406 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
407 | });
408 | });
409 | });
410 | });
411 |
412 | context('mozTelephony', function () {
413 | context('safe', function () {
414 | context(null, function () {
415 | var good = 'var a = "window.navigator.mozTelephony;";';
416 | it(good, function () {
417 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
418 | });
419 | });
420 | });
421 | context('dangerous', function () {
422 | context(null, function () {
423 | var bad = 'var phone = window.navigator.mozTelephony;';
424 | it(bad, function () {
425 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
426 | });
427 | });
428 | });
429 | });
430 |
431 | context('mozTime', function () {
432 | context('safe', function () {
433 | context(null, function () {
434 | var good = 'var a = "mozTime;";';
435 | it(good, function () {
436 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
437 | });
438 | });
439 | });
440 | context('dangerous', function () {
441 | context(null, function () {
442 | var bad = 'var time = window.navigator.mozTime;';
443 | it(bad, function () {
444 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
445 | });
446 | });
447 | });
448 | });
449 |
450 | context('mozVoicemail', function () {
451 | context('safe', function () {
452 | context(null, function () {
453 | var good = 'var a = "mozVoicemail";';
454 | it(good, function () {
455 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
456 | });
457 | });
458 | });
459 | context('dangerous', function () {
460 | context(null, function () {
461 | var bad = 'var voicemail = window.navigator.mozVoicemail;';
462 | it(bad, function () {
463 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
464 | });
465 | });
466 | context(null, function () {
467 | var bad = 'var status = window.navigator.mozVoicemail.getStatus();';
468 | it(bad, function () {
469 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
470 | });
471 | });
472 | });
473 | });
474 |
475 | context('mozapp', function () {
476 | context('safe', function () {
477 | context(null, function () {
478 | var good = 'var a = "mozapp";';
479 | it(good, function () {
480 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
481 | });
482 | });
483 | });
484 | context('dangerous', function () {
485 | /* deactivated, failing test.
486 | context(null, function () {
487 | var bad = 'var a = document.createElement("iframe"); a.mozapp = data.app; document.appendChild(a);';
488 | it(bad, function () {
489 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
490 | });
491 | });
492 | */
493 | context(null, function () {
494 | // issue 73 - https://github.com/mozilla/scanjs/issues/73
495 | var bad = 'iframe.setAttribute("mozapp", data.app);';
496 | it.skip(bad, function () {
497 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
498 | });
499 | });
500 | });
501 | });
502 |
503 | context('mozaudiochannel', function () {
504 | context('safe', function () {
505 | context(null, function () {
506 | var good = 'var a = "mozaudiochannel=content.toString()";';
507 | it(good, function () {
508 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
509 | });
510 | });
511 | });
512 | context('dangerous', function () {
513 | /* deactivated, failing test.
514 | context(null, function () {
515 | var bad = 'var a = document.createElement("audio"); a.mozaudiochannel = "content"; document.appendChild(a);';
516 | it(bad, function () {
517 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
518 | });
519 | });
520 | */
521 | context(null, function () {
522 | // issue 73 - https://github.com/mozilla/scanjs/issues/73
523 | var bad = 'var a = document.createElement("audio"); a.setAttribute("mozaudiochannel", data.app);';
524 | it.skip(bad, function () {
525 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
526 | });
527 | });
528 | });
529 | });
530 |
531 | context('moznetworkdownload', function () {
532 | context('safe', function () {
533 | context(null, function () {
534 | var good = 'var a = "moznetworkdownload";';
535 | it(good, function () {
536 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
537 | });
538 | });
539 | });
540 | context('dangerous', function () {
541 | /* deactivated, failing test.
542 | context(null, function () {
543 | var bad = 'addEventListener("moznetworkdownload", downloadHandler);';
544 | it(bad, function () {
545 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
546 | });
547 | });
548 | */
549 | });
550 | });
551 |
552 | context('moznetworkupload', function () {
553 | context('safe', function () {
554 | context(null, function () {
555 | var good = 'var a = "moznetworkupload";';
556 | it(good, function () {
557 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
558 | });
559 | });
560 | });
561 | context('dangerous', function () {
562 | /* deactivated, failing test.
563 | context(null, function () {
564 | var bad = 'addEventListener("moznetworkupload", downloadHandler);';
565 | it(bad, function () {
566 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
567 | });
568 | });
569 | */
570 | });
571 | });
572 |
573 | context('mozWifiManager', function () {
574 | context('safe', function () {
575 | context(null, function () {
576 | var good = 'var a = "mozWifiManager";';
577 | it(good, function () {
578 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
579 | });
580 | });
581 | context(null, function () {
582 | var good = 'somethingNotNavigator.mozWifiManager;';
583 | it(good, function () {
584 | chai.expect(ScanJS.scan(acorn.parse(good, {locations : true}), "/tests/")).to.be.empty;
585 | });
586 | });
587 | });
588 | context('dangerous', function () {
589 | context(null, function () {
590 | var bad = 'var wifi = navigator.mozWifiManager;';
591 | it(bad, function () {
592 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
593 | });
594 | });
595 | context(null, function () {
596 | var bad = 'var networks = navigator.mozWifiManager.getNetworks();';
597 | it(bad, function () {
598 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
599 | });
600 | });
601 | context(null, function () {
602 | var bad = 'var wifi = navigator.mozWifiManager;';
603 | it(bad, function () {
604 | chai.expect(ScanJS.scan(acorn.parse(bad, {locations : true}), "/tests/")).not.to.be.empty;
605 | });
606 | });
607 | });
608 | });
609 |
610 | })(); //describe('Mozilla specific tests'...
611 |
--------------------------------------------------------------------------------
/client/js/lib/acorn_loose.js:
--------------------------------------------------------------------------------
1 | // Acorn: Loose parser
2 | //
3 | // This module provides an alternative parser (`parse_dammit`) that
4 | // exposes that same interface as `parse`, but will try to parse
5 | // anything as JavaScript, repairing syntax error the best it can.
6 | // There are circumstances in which it will raise an error and give
7 | // up, but they are very rare. The resulting AST will be a mostly
8 | // valid JavaScript AST (as per the [Mozilla parser API][api], except
9 | // that:
10 | //
11 | // - Return outside functions is allowed
12 | //
13 | // - Label consistency (no conflicts, break only to existing labels)
14 | // is not enforced.
15 | //
16 | // - Bogus Identifier nodes with a name of `"✖"` are inserted whenever
17 | // the parser got too confused to return anything meaningful.
18 | //
19 | // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
20 | //
21 | // The expected use for this is to *first* try `acorn.parse`, and only
22 | // if that fails switch to `parse_dammit`. The loose parser might
23 | // parse badly indented code incorrectly, so **don't** use it as
24 | // your default parser.
25 | //
26 | // Quite a lot of acorn.js is duplicated here. The alternative was to
27 | // add a *lot* of extra cruft to that file, making it less readable
28 | // and slower. Copying and editing the code allowed me to make
29 | // invasive changes and simplifications without creating a complicated
30 | // tangle.
31 |
32 | (function(mod) {
33 | if (typeof exports == "object" && typeof module == "object") return mod(exports, require("./acorn")); // CommonJS
34 | if (typeof define == "function" && define.amd) return define(["exports", "./acorn"], mod); // AMD
35 | mod(this.acorn || (this.acorn = {}), this.acorn); // Plain browser env
36 | })(function(exports, acorn) {
37 | "use strict";
38 |
39 | var tt = acorn.tokTypes;
40 |
41 | var options, input, fetchToken, context;
42 |
43 | exports.parse_dammit = function(inpt, opts) {
44 | if (!opts) opts = {};
45 | input = String(inpt);
46 | options = opts;
47 | if (!opts.tabSize) opts.tabSize = 4;
48 | fetchToken = acorn.tokenize(inpt, opts);
49 | sourceFile = options.sourceFile || null;
50 | context = [];
51 | nextLineStart = 0;
52 | ahead.length = 0;
53 | next();
54 | return parseTopLevel();
55 | };
56 |
57 | var lastEnd, token = {start: 0, end: 0}, ahead = [];
58 | var curLineStart, nextLineStart, curIndent, lastEndLoc, sourceFile;
59 |
60 | function next() {
61 | lastEnd = token.end;
62 | if (options.locations)
63 | lastEndLoc = token.endLoc;
64 |
65 | if (ahead.length)
66 | token = ahead.shift();
67 | else
68 | token = readToken();
69 |
70 | if (token.start >= nextLineStart) {
71 | while (token.start >= nextLineStart) {
72 | curLineStart = nextLineStart;
73 | nextLineStart = lineEnd(curLineStart) + 1;
74 | }
75 | curIndent = indentationAfter(curLineStart);
76 | }
77 | }
78 |
79 | function readToken() {
80 | for (;;) {
81 | try {
82 | return fetchToken();
83 | } catch(e) {
84 | if (!(e instanceof SyntaxError)) throw e;
85 |
86 | // Try to skip some text, based on the error message, and then continue
87 | var msg = e.message, pos = e.raisedAt, replace = true;
88 | if (/unterminated/i.test(msg)) {
89 | pos = lineEnd(e.pos);
90 | if (/string/.test(msg)) {
91 | replace = {start: e.pos, end: pos, type: tt.string, value: input.slice(e.pos + 1, pos)};
92 | } else if (/regular expr/i.test(msg)) {
93 | var re = input.slice(e.pos, pos);
94 | try { re = new RegExp(re); } catch(e) {}
95 | replace = {start: e.pos, end: pos, type: tt.regexp, value: re};
96 | } else {
97 | replace = false;
98 | }
99 | } else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number/i.test(msg)) {
100 | while (pos < input.length && !isSpace(input.charCodeAt(pos))) ++pos;
101 | } else if (/character escape|expected hexadecimal/i.test(msg)) {
102 | while (pos < input.length) {
103 | var ch = input.charCodeAt(pos++);
104 | if (ch === 34 || ch === 39 || isNewline(ch)) break;
105 | }
106 | } else if (/unexpected character/i.test(msg)) {
107 | pos++;
108 | replace = false;
109 | } else {
110 | throw e;
111 | }
112 | resetTo(pos);
113 | if (replace === true) replace = {start: pos, end: pos, type: tt.name, value: "✖"};
114 | if (replace) {
115 | if (options.locations) {
116 | replace.startLoc = acorn.getLineInfo(input, replace.start);
117 | replace.endLoc = acorn.getLineInfo(input, replace.end);
118 | }
119 | return replace;
120 | }
121 | }
122 | }
123 | }
124 |
125 | function resetTo(pos) {
126 | var ch = input.charAt(pos - 1);
127 | var reAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) ||
128 | /[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(input.slice(pos - 10, pos));
129 | fetchToken.jumpTo(pos, reAllowed);
130 | }
131 |
132 | function copyToken(token) {
133 | var copy = {start: token.start, end: token.end, type: token.type, value: token.value};
134 | if (options.locations) {
135 | copy.startLoc = token.startLoc;
136 | copy.endLoc = token.endLoc;
137 | }
138 | return copy;
139 | }
140 |
141 | function lookAhead(n) {
142 | // Copy token objects, because fetchToken will overwrite the one
143 | // it returns, and in this case we still need it
144 | if (!ahead.length)
145 | token = copyToken(token);
146 | while (n > ahead.length)
147 | ahead.push(copyToken(readToken()));
148 | return ahead[n-1];
149 | }
150 |
151 | var newline = /[\n\r\u2028\u2029]/;
152 |
153 | function isNewline(ch) {
154 | return ch === 10 || ch === 13 || ch === 8232 || ch === 8329;
155 | }
156 | function isSpace(ch) {
157 | return (ch < 14 && ch > 8) || ch === 32 || ch === 160 || isNewline(ch);
158 | }
159 |
160 | function pushCx() {
161 | context.push(curIndent);
162 | }
163 | function popCx() {
164 | curIndent = context.pop();
165 | }
166 |
167 | function lineEnd(pos) {
168 | while (pos < input.length && !isNewline(input.charCodeAt(pos))) ++pos;
169 | return pos;
170 | }
171 | function lineStart(pos) {
172 | while (pos > 0 && !isNewline(input.charCodeAt(pos - 1))) --pos;
173 | return pos;
174 | }
175 | function indentationAfter(pos) {
176 | for (var count = 0;; ++pos) {
177 | var ch = input.charCodeAt(pos);
178 | if (ch === 32) ++count;
179 | else if (ch === 9) count += options.tabSize;
180 | else return count;
181 | }
182 | }
183 |
184 | function closes(closeTok, indent, line, blockHeuristic) {
185 | if (token.type === closeTok || token.type === tt.eof) return true;
186 | if (line != curLineStart && curIndent < indent && tokenStartsLine() &&
187 | (!blockHeuristic || nextLineStart >= input.length ||
188 | indentationAfter(nextLineStart) < indent)) return true;
189 | return false;
190 | }
191 |
192 | function tokenStartsLine() {
193 | for (var p = token.start - 1; p >= curLineStart; --p) {
194 | var ch = input.charCodeAt(p);
195 | if (ch !== 9 && ch !== 32) return false;
196 | }
197 | return true;
198 | }
199 |
200 | function node_t(start) {
201 | this.type = null;
202 | this.start = start;
203 | this.end = null;
204 | }
205 |
206 | function node_loc_t(start) {
207 | this.start = start || token.startLoc || {line: 1, column: 0};
208 | this.end = null;
209 | if (sourceFile !== null) this.source = sourceFile;
210 | }
211 |
212 | function startNode() {
213 | var node = new node_t(token.start);
214 | if (options.locations)
215 | node.loc = new node_loc_t();
216 | return node;
217 | }
218 |
219 | function startNodeFrom(other) {
220 | var node = new node_t(other.start);
221 | if (options.locations)
222 | node.loc = new node_loc_t(other.loc.start);
223 | return node;
224 | }
225 |
226 | function finishNode(node, type) {
227 | node.type = type;
228 | node.end = lastEnd;
229 | if (options.locations)
230 | node.loc.end = lastEndLoc;
231 | return node;
232 | }
233 |
234 | function getDummyLoc() {
235 | if (options.locations) {
236 | var loc = new node_loc_t();
237 | loc.end = loc.start;
238 | return loc;
239 | }
240 | };
241 |
242 | function dummyIdent() {
243 | var dummy = new node_t(token.start);
244 | dummy.type = "Identifier";
245 | dummy.end = token.start;
246 | dummy.name = "✖";
247 | dummy.loc = getDummyLoc();
248 | return dummy;
249 | }
250 | function isDummy(node) { return node.name == "✖"; }
251 |
252 | function eat(type) {
253 | if (token.type === type) {
254 | next();
255 | return true;
256 | }
257 | }
258 |
259 | function canInsertSemicolon() {
260 | return (token.type === tt.eof || token.type === tt.braceR || newline.test(input.slice(lastEnd, token.start)));
261 | }
262 | function semicolon() {
263 | eat(tt.semi);
264 | }
265 |
266 | function expect(type) {
267 | if (eat(type)) return true;
268 | if (lookAhead(1).type == type) {
269 | next(); next();
270 | return true;
271 | }
272 | if (lookAhead(2).type == type) {
273 | next(); next(); next();
274 | return true;
275 | }
276 | }
277 |
278 | function checkLVal(expr) {
279 | if (expr.type === "Identifier" || expr.type === "MemberExpression") return expr;
280 | return dummyIdent();
281 | }
282 |
283 | function parseTopLevel() {
284 | var node = startNode();
285 | node.body = [];
286 | while (token.type !== tt.eof) node.body.push(parseStatement());
287 | return finishNode(node, "Program");
288 | }
289 |
290 | function parseStatement() {
291 | var starttype = token.type, node = startNode();
292 |
293 | switch (starttype) {
294 | case tt._break: case tt._continue:
295 | next();
296 | var isBreak = starttype === tt._break;
297 | node.label = token.type === tt.name ? parseIdent() : null;
298 | semicolon();
299 | return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
300 |
301 | case tt._debugger:
302 | next();
303 | semicolon();
304 | return finishNode(node, "DebuggerStatement");
305 |
306 | case tt._do:
307 | next();
308 | node.body = parseStatement();
309 | node.test = eat(tt._while) ? parseParenExpression() : dummyIdent();
310 | semicolon();
311 | return finishNode(node, "DoWhileStatement");
312 |
313 | case tt._for:
314 | next();
315 | pushCx();
316 | expect(tt.parenL);
317 | if (token.type === tt.semi) return parseFor(node, null);
318 | if (token.type === tt._var) {
319 | var init = startNode();
320 | next();
321 | parseVar(init, true);
322 | if (init.declarations.length === 1 && eat(tt._in))
323 | return parseForIn(node, init);
324 | return parseFor(node, init);
325 | }
326 | var init = parseExpression(false, true);
327 | if (eat(tt._in)) {return parseForIn(node, checkLVal(init));}
328 | return parseFor(node, init);
329 |
330 | case tt._function:
331 | next();
332 | return parseFunction(node, true);
333 |
334 | case tt._if:
335 | next();
336 | node.test = parseParenExpression();
337 | node.consequent = parseStatement();
338 | node.alternate = eat(tt._else) ? parseStatement() : null;
339 | return finishNode(node, "IfStatement");
340 |
341 | case tt._return:
342 | next();
343 | if (eat(tt.semi) || canInsertSemicolon()) node.argument = null;
344 | else { node.argument = parseExpression(); semicolon(); }
345 | return finishNode(node, "ReturnStatement");
346 |
347 | case tt._switch:
348 | var blockIndent = curIndent, line = curLineStart;
349 | next();
350 | node.discriminant = parseParenExpression();
351 | node.cases = [];
352 | pushCx();
353 | expect(tt.braceL);
354 |
355 | for (var cur; !closes(tt.braceR, blockIndent, line, true);) {
356 | if (token.type === tt._case || token.type === tt._default) {
357 | var isCase = token.type === tt._case;
358 | if (cur) finishNode(cur, "SwitchCase");
359 | node.cases.push(cur = startNode());
360 | cur.consequent = [];
361 | next();
362 | if (isCase) cur.test = parseExpression();
363 | else cur.test = null;
364 | expect(tt.colon);
365 | } else {
366 | if (!cur) {
367 | node.cases.push(cur = startNode());
368 | cur.consequent = [];
369 | cur.test = null;
370 | }
371 | cur.consequent.push(parseStatement());
372 | }
373 | }
374 | if (cur) finishNode(cur, "SwitchCase");
375 | popCx();
376 | eat(tt.braceR);
377 | return finishNode(node, "SwitchStatement");
378 |
379 | case tt._throw:
380 | next();
381 | node.argument = parseExpression();
382 | semicolon();
383 | return finishNode(node, "ThrowStatement");
384 |
385 | case tt._try:
386 | next();
387 | node.block = parseBlock();
388 | node.handler = null;
389 | if (token.type === tt._catch) {
390 | var clause = startNode();
391 | next();
392 | expect(tt.parenL);
393 | clause.param = parseIdent();
394 | expect(tt.parenR);
395 | clause.guard = null;
396 | clause.body = parseBlock();
397 | node.handler = finishNode(clause, "CatchClause");
398 | }
399 | node.finalizer = eat(tt._finally) ? parseBlock() : null;
400 | if (!node.handler && !node.finalizer) return node.block;
401 | return finishNode(node, "TryStatement");
402 |
403 | case tt._var:
404 | next();
405 | node = parseVar(node);
406 | semicolon();
407 | return node;
408 |
409 | case tt._while:
410 | next();
411 | node.test = parseParenExpression();
412 | node.body = parseStatement();
413 | return finishNode(node, "WhileStatement");
414 |
415 | case tt._with:
416 | next();
417 | node.object = parseParenExpression();
418 | node.body = parseStatement();
419 | return finishNode(node, "WithStatement");
420 |
421 | case tt.braceL:
422 | return parseBlock();
423 |
424 | case tt.semi:
425 | next();
426 | return finishNode(node, "EmptyStatement");
427 |
428 | default:
429 | var maybeName = token.value, expr = parseExpression();
430 | if (isDummy(expr)) {
431 | next();
432 | if (token.type === tt.eof) return finishNode(node, "EmptyStatement");
433 | return parseStatement();
434 | } else if (starttype === tt.name && expr.type === "Identifier" && eat(tt.colon)) {
435 | node.body = parseStatement();
436 | node.label = expr;
437 | return finishNode(node, "LabeledStatement");
438 | } else {
439 | node.expression = expr;
440 | semicolon();
441 | return finishNode(node, "ExpressionStatement");
442 | }
443 | }
444 | }
445 |
446 | function parseBlock() {
447 | var node = startNode();
448 | pushCx();
449 | expect(tt.braceL);
450 | var blockIndent = curIndent, line = curLineStart;
451 | node.body = [];
452 | while (!closes(tt.braceR, blockIndent, line, true))
453 | node.body.push(parseStatement());
454 | popCx();
455 | eat(tt.braceR);
456 | return finishNode(node, "BlockStatement");
457 | }
458 |
459 | function parseFor(node, init) {
460 | node.init = init;
461 | node.test = node.update = null;
462 | if (eat(tt.semi) && token.type !== tt.semi) node.test = parseExpression();
463 | if (eat(tt.semi) && token.type !== tt.parenR) node.update = parseExpression();
464 | popCx();
465 | expect(tt.parenR);
466 | node.body = parseStatement();
467 | return finishNode(node, "ForStatement");
468 | }
469 |
470 | function parseForIn(node, init) {
471 | node.left = init;
472 | node.right = parseExpression();
473 | popCx();
474 | expect(tt.parenR);
475 | node.body = parseStatement();
476 | return finishNode(node, "ForInStatement");
477 | }
478 |
479 | function parseVar(node, noIn) {
480 | node.declarations = [];
481 | node.kind = "var";
482 | while (token.type === tt.name) {
483 | var decl = startNode();
484 | decl.id = parseIdent();
485 | decl.init = eat(tt.eq) ? parseExpression(true, noIn) : null;
486 | node.declarations.push(finishNode(decl, "VariableDeclarator"));
487 | if (!eat(tt.comma)) break;
488 | }
489 | return finishNode(node, "VariableDeclaration");
490 | }
491 |
492 | function parseExpression(noComma, noIn) {
493 | var expr = parseMaybeAssign(noIn);
494 | if (!noComma && token.type === tt.comma) {
495 | var node = startNodeFrom(expr);
496 | node.expressions = [expr];
497 | while (eat(tt.comma)) node.expressions.push(parseMaybeAssign(noIn));
498 | return finishNode(node, "SequenceExpression");
499 | }
500 | return expr;
501 | }
502 |
503 | function parseParenExpression() {
504 | pushCx();
505 | expect(tt.parenL);
506 | var val = parseExpression();
507 | popCx();
508 | expect(tt.parenR);
509 | return val;
510 | }
511 |
512 | function parseMaybeAssign(noIn) {
513 | var left = parseMaybeConditional(noIn);
514 | if (token.type.isAssign) {
515 | var node = startNodeFrom(left);
516 | node.operator = token.value;
517 | node.left = checkLVal(left);
518 | next();
519 | node.right = parseMaybeAssign(noIn);
520 | return finishNode(node, "AssignmentExpression");
521 | }
522 | return left;
523 | }
524 |
525 | function parseMaybeConditional(noIn) {
526 | var expr = parseExprOps(noIn);
527 | if (eat(tt.question)) {
528 | var node = startNodeFrom(expr);
529 | node.test = expr;
530 | node.consequent = parseExpression(true);
531 | node.alternate = expect(tt.colon) ? parseExpression(true, noIn) : dummyIdent();
532 | return finishNode(node, "ConditionalExpression");
533 | }
534 | return expr;
535 | }
536 |
537 | function parseExprOps(noIn) {
538 | var indent = curIndent, line = curLineStart;
539 | return parseExprOp(parseMaybeUnary(noIn), -1, noIn, indent, line);
540 | }
541 |
542 | function parseExprOp(left, minPrec, noIn, indent, line) {
543 | if (curLineStart != line && curIndent < indent && tokenStartsLine()) return left;
544 | var prec = token.type.binop;
545 | if (prec != null && (!noIn || token.type !== tt._in)) {
546 | if (prec > minPrec) {
547 | var node = startNodeFrom(left);
548 | node.left = left;
549 | node.operator = token.value;
550 | next();
551 | if (curLineStart != line && curIndent < indent && tokenStartsLine())
552 | node.right = dummyIdent();
553 | else
554 | node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn, indent, line);
555 | var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
556 | return parseExprOp(node, minPrec, noIn, indent, line);
557 | }
558 | }
559 | return left;
560 | }
561 |
562 | function parseMaybeUnary(noIn) {
563 | if (token.type.prefix) {
564 | var node = startNode(), update = token.type.isUpdate;
565 | node.operator = token.value;
566 | node.prefix = true;
567 | next();
568 | node.argument = parseMaybeUnary(noIn);
569 | if (update) node.argument = checkLVal(node.argument);
570 | return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
571 | }
572 | var expr = parseExprSubscripts();
573 | while (token.type.postfix && !canInsertSemicolon()) {
574 | var node = startNodeFrom(expr);
575 | node.operator = token.value;
576 | node.prefix = false;
577 | node.argument = checkLVal(expr);
578 | next();
579 | expr = finishNode(node, "UpdateExpression");
580 | }
581 | return expr;
582 | }
583 |
584 | function parseExprSubscripts() {
585 | var indent = curIndent, line = curLineStart;
586 | return parseSubscripts(parseExprAtom(), false, curIndent, line);
587 | }
588 |
589 | function parseSubscripts(base, noCalls, startIndent, line) {
590 | for (;;) {
591 | if (curLineStart != line && curIndent <= startIndent && tokenStartsLine()) {
592 | if (token.type == tt.dot && curIndent == startIndent)
593 | --startIndent;
594 | else
595 | return base;
596 | }
597 |
598 | if (eat(tt.dot)) {
599 | var node = startNodeFrom(base);
600 | node.object = base;
601 | if (curLineStart != line && curIndent <= startIndent && tokenStartsLine())
602 | node.property = dummyIdent();
603 | else
604 | node.property = parsePropertyName() || dummyIdent();
605 | node.computed = false;
606 | base = finishNode(node, "MemberExpression");
607 | } else if (token.type == tt.bracketL) {
608 | pushCx();
609 | next();
610 | var node = startNodeFrom(base);
611 | node.object = base;
612 | node.property = parseExpression();
613 | node.computed = true;
614 | popCx();
615 | expect(tt.bracketR);
616 | base = finishNode(node, "MemberExpression");
617 | } else if (!noCalls && token.type == tt.parenL) {
618 | pushCx();
619 | var node = startNodeFrom(base);
620 | node.callee = base;
621 | node.arguments = parseExprList(tt.parenR);
622 | base = finishNode(node, "CallExpression");
623 | } else {
624 | return base;
625 | }
626 | }
627 | }
628 |
629 | function parseExprAtom() {
630 | switch (token.type) {
631 | case tt._this:
632 | var node = startNode();
633 | next();
634 | return finishNode(node, "ThisExpression");
635 | case tt.name:
636 | return parseIdent();
637 | case tt.num: case tt.string: case tt.regexp:
638 | var node = startNode();
639 | node.value = token.value;
640 | node.raw = input.slice(token.start, token.end);
641 | next();
642 | return finishNode(node, "Literal");
643 |
644 | case tt._null: case tt._true: case tt._false:
645 | var node = startNode();
646 | node.value = token.type.atomValue;
647 | node.raw = token.type.keyword;
648 | next();
649 | return finishNode(node, "Literal");
650 |
651 | case tt.parenL:
652 | var tokStart1 = token.start;
653 | next();
654 | var val = parseExpression();
655 | val.start = tokStart1;
656 | val.end = token.end;
657 | expect(tt.parenR);
658 | return val;
659 |
660 | case tt.bracketL:
661 | var node = startNode();
662 | pushCx();
663 | node.elements = parseExprList(tt.bracketR);
664 | return finishNode(node, "ArrayExpression");
665 |
666 | case tt.braceL:
667 | return parseObj();
668 |
669 | case tt._function:
670 | var node = startNode();
671 | next();
672 | return parseFunction(node, false);
673 |
674 | case tt._new:
675 | return parseNew();
676 |
677 | default:
678 | return dummyIdent();
679 | }
680 | }
681 |
682 | function parseNew() {
683 | var node = startNode(), startIndent = curIndent, line = curLineStart;
684 | next();
685 | node.callee = parseSubscripts(parseExprAtom(), true, startIndent, line);
686 | if (token.type == tt.parenL) {
687 | pushCx();
688 | node.arguments = parseExprList(tt.parenR);
689 | } else {
690 | node.arguments = [];
691 | }
692 | return finishNode(node, "NewExpression");
693 | }
694 |
695 | function parseObj() {
696 | var node = startNode();
697 | node.properties = [];
698 | pushCx();
699 | next();
700 | var propIndent = curIndent, line = curLineStart;
701 | while (!closes(tt.braceR, propIndent, line)) {
702 | var name = parsePropertyName();
703 | if (!name) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; }
704 | var prop = {key: name}, isGetSet = false, kind;
705 | if (eat(tt.colon)) {
706 | prop.value = parseExpression(true);
707 | kind = prop.kind = "init";
708 | } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
709 | (prop.key.name === "get" || prop.key.name === "set")) {
710 | isGetSet = true;
711 | kind = prop.kind = prop.key.name;
712 | prop.key = parsePropertyName() || dummyIdent();
713 | prop.value = parseFunction(startNode(), false);
714 | } else {
715 | next();
716 | eat(tt.comma);
717 | continue;
718 | }
719 |
720 | node.properties.push(prop);
721 | eat(tt.comma);
722 | }
723 | popCx();
724 | eat(tt.braceR);
725 | return finishNode(node, "ObjectExpression");
726 | }
727 |
728 | function parsePropertyName() {
729 | if (token.type === tt.num || token.type === tt.string) return parseExprAtom();
730 | if (token.type === tt.name || token.type.keyword) return parseIdent();
731 | }
732 |
733 | function parseIdent() {
734 | var node = startNode();
735 | node.name = token.type === tt.name ? token.value : token.type.keyword;
736 | next();
737 | return finishNode(node, "Identifier");
738 | }
739 |
740 | function parseFunction(node, isStatement) {
741 | if (token.type === tt.name) node.id = parseIdent();
742 | else if (isStatement) node.id = dummyIdent();
743 | else node.id = null;
744 | node.params = [];
745 | pushCx();
746 | expect(tt.parenL);
747 | while (token.type == tt.name) {
748 | node.params.push(parseIdent());
749 | eat(tt.comma);
750 | }
751 | popCx();
752 | eat(tt.parenR);
753 | node.body = parseBlock();
754 | return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
755 | }
756 |
757 | function parseExprList(close) {
758 | var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart;
759 | next(); // Opening bracket
760 | while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) {
761 | var elt = parseExpression(true);
762 | if (isDummy(elt)) {
763 | if (closes(close, indent, line)) break;
764 | next();
765 | } else {
766 | elts.push(elt);
767 | }
768 | while (eat(tt.comma)) {}
769 | }
770 | popCx();
771 | eat(close);
772 | return elts;
773 | }
774 | });
775 |
--------------------------------------------------------------------------------