From eca77528d6c5047d9f1e0b0dc0726189319a59d2 Mon Sep 17 00:00:00 2001 From: Elbert Alias <77259+AliasIO@users.noreply.github.com> Date: Fri, 10 Aug 2018 13:57:03 +1000 Subject: [PATCH] Add eslint --- .eslintignore | 2 + .eslintrc.js | 6 + npm-shrinkwrap.json | 1545 ++++++++++++++++- package.json | 7 +- src/drivers/bookmarklet/driver.js | 104 +- src/drivers/npm/driver.js | 285 +-- src/drivers/npm/index.js | 29 +- src/drivers/webextension/html/background.html | 2 +- src/drivers/webextension/html/popup.html | 2 +- src/drivers/webextension/js/content.js | 14 +- src/drivers/webextension/js/driver.js | 132 +- src/drivers/webextension/js/iframe.js | 1185 ------------- src/drivers/webextension/js/inject.js | 18 +- src/drivers/webextension/js/jsontodom.js | 63 - src/drivers/webextension/js/lib/iframe.js | 1140 ++++++++++++ src/drivers/webextension/js/lib/jsontodom.js | 63 + src/drivers/webextension/js/lib/network.js | 793 +++++++++ src/drivers/webextension/js/network.js | 824 --------- src/drivers/webextension/js/options.js | 16 +- src/drivers/webextension/js/popup.js | 102 +- src/drivers/webextension/manifest.json | 2 +- src/wappalyzer.js | 507 +++--- 22 files changed, 4163 insertions(+), 2678 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.js delete mode 100644 src/drivers/webextension/js/iframe.js delete mode 100644 src/drivers/webextension/js/jsontodom.js create mode 100644 src/drivers/webextension/js/lib/iframe.js create mode 100644 src/drivers/webextension/js/lib/jsontodom.js create mode 100644 src/drivers/webextension/js/lib/network.js delete mode 100644 src/drivers/webextension/js/network.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..63190aca9 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +**/lib/* +node_modules diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..d262d9909 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + "extends": "airbnb-base", + "rules": { + "no-param-reassign": 0 + } +}; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 86c50adb4..221001f31 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2,12 +2,142 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "acorn": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -30,6 +160,33 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", @@ -44,12 +201,82 @@ "type-detect": "^4.0.0" } }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", @@ -62,6 +289,41 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -80,29 +342,404 @@ "type-detect": "^4.0.0" } }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "^2.0.5", + "object-keys": "^1.0.8" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + } + }, + "eslint-config-airbnb-base": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.0.0.tgz", + "integrity": "sha512-hUFXRlE6AY84z0qYh4wKdtSF4EqDnyT8sxrvTpcXCV4ENSLF8li5yNA1yDM26iinH8Ierbpc4lv8Rp62uX6VSQ==", + "dev": true, + "requires": { + "eslint-restricted-globals": "^0.1.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.13.0.tgz", + "integrity": "sha512-t6hGKQDMIt9N8R7vLepsYXgDfeuhp6ZJSgtrLEDxonpSubyxUZHjhm6LsAaZX8q6GYVxkbT3kTsV9G5mBCFR6A==", + "dev": true, + "requires": { + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, "file-type": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-7.4.0.tgz", "integrity": "sha1-KnyU9ioAMBULt9m2xwz6HT51nIY=" }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -123,29 +760,114 @@ "path-is-absolute": "^1.0.0" } }, + "globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, "html-comment-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=" }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -162,40 +884,256 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "minimist": "0.0.8" + "builtin-modules": "^1.0.0" } }, - "mocha": { - "version": "5.2.0", + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-svg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, @@ -219,6 +1157,66 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -228,12 +1226,112 @@ "wrappy": "1" } }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", @@ -245,6 +1343,60 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "read-chunk": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", @@ -254,11 +1406,260 @@ "safe-buffer": "^5.1.1" } }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -268,17 +1669,113 @@ "has-flag": "^3.0.0" } }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true } } } diff --git a/package.json b/package.json index 7f1cc55b6..be41133c7 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,14 @@ }, "devDependencies": { "chai": "^4.1.2", + "eslint": "^4.19.1", + "eslint-config-airbnb-base": "^13.0.0", + "eslint-plugin-import": "^2.13.0", "mocha": "^5.2.0" }, "scripts": { - "test": "mocha -R spec ./test" + "test": "mocha -R spec ./test", + "lint": "eslint src", + "lint:fix": "eslint src --fix" } } diff --git a/src/drivers/bookmarklet/driver.js b/src/drivers/bookmarklet/driver.js index 3e6793b39..cd2b2af37 100644 --- a/src/drivers/bookmarklet/driver.js +++ b/src/drivers/bookmarklet/driver.js @@ -5,29 +5,29 @@ /** global: wappalyzer */ /** global: XMLHttpRequest */ -(function() { +(function () { wappalyzer.driver.document = document; - const container = document.getElementById('wappalyzer-container'); - const url = wappalyzer.parseUrl(window.top.location.href); - const hasOwn = Object.prototype.hasOwnProperty; + const container = document.getElementById('wappalyzer-container'); + const url = wappalyzer.parseUrl(window.top.location.href); + const hasOwn = Object.prototype.hasOwnProperty; /** * Log messages to console */ wappalyzer.driver.log = (message, source, type) => { - console.log('[wappalyzer ' + type + ']', '[' + source + ']', message); + console.log(`[wappalyzer ${type}]`, `[${source}]`, message); }; function getPageContent() { wappalyzer.log('func: getPageContent', 'driver'); - var scripts = Array.prototype.slice + const scripts = Array.prototype.slice .apply(document.scripts) .filter(s => s.src) .map(s => s.src); - var html = new window.XMLSerializer().serializeToString(document).split('\n'); + let html = new window.XMLSerializer().serializeToString(document).split('\n'); html = html .slice(0, 1000).concat(html.slice(html.length - 1000)) @@ -35,47 +35,48 @@ .join('\n'); wappalyzer.analyze(url, { - html: html, - scripts: scripts + html, + scripts, }); } - function getResponseHeaders() { + function getResponseHeaders() { wappalyzer.log('func: getResponseHeaders', 'driver'); - var xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = () => { - if ( xhr.readyState === 4 && xhr.status ) { - var headers = xhr.getAllResponseHeaders().split("\n"); + if (xhr.readyState === 4 && xhr.status) { + const headers = xhr.getAllResponseHeaders().split('\n'); - if ( headers.length > 0 && headers[0] != '' ) { - wappalyzer.log('responseHeaders: ' + xhr.getAllResponseHeaders(), 'driver'); + if (headers.length > 0 && headers[0] != '') { + wappalyzer.log(`responseHeaders: ${xhr.getAllResponseHeaders()}`, 'driver'); - var responseHeaders = {}; + const responseHeaders = {}; - headers.forEach(line => { - var name, value; + headers.forEach((line) => { + let name, + value; - if ( line ) { - name = line.substring(0, line.indexOf(': ')); + if (line) { + name = line.substring(0, line.indexOf(': ')); value = line.substring(line.indexOf(': ') + 2, line.length - 1); - if ( !responseHeaders[name.toLowerCase()] ){ - responseHeaders[name.toLowerCase()] = [] + if (!responseHeaders[name.toLowerCase()]) { + responseHeaders[name.toLowerCase()] = []; } responseHeaders[name.toLowerCase()].push(value); } }); wappalyzer.analyze(url, { - headers: responseHeaders + headers: responseHeaders, }); } } - } + }; xhr.send(); } @@ -83,46 +84,45 @@ /** * Display apps */ - wappalyzer.driver.displayApps = detected => { + wappalyzer.driver.displayApps = (detected) => { wappalyzer.log('func: diplayApps', 'driver'); - var first = true; - var app; - var category; - var html; + let first = true; + let app; + let category; + let html; - html = - '' + - 'Close' + - '' + - '
'; + html = '' + + 'Close' + + '' + + '
'; - if ( detected != null && Object.keys(detected).length ) { - for ( app in detected ) { - if ( !hasOwn.call(detected, app) ) { + if (detected != null && Object.keys(detected).length) { + for (app in detected) { + if (!hasOwn.call(detected, app)) { continue; } - var version = detected[app].version, + let version = detected[app].version, confidence = detected[app].confidence; - html += - '
' + - '' + - '' + - ' ' + app + - '' + - ( version ? ' ' + version : '' ) + ( confidence < 100 ? ' (' + confidence + '% sure)' : '' ) + - ''; - - for ( let i in wappalyzer.apps[app].cats ) { - if ( !hasOwn.call(wappalyzer.apps[app].cats, i) ) { + html + += `
` + + `` + + '' + + ` ${app + }${ + version ? ` ${version}` : ''}${confidence < 100 ? ` (${confidence}% sure)` : '' + }`; + + for (const i in wappalyzer.apps[app].cats) { + if (!hasOwn.call(wappalyzer.apps[app].cats, i)) { continue; } category = wappalyzer.categories[wappalyzer.apps[app].cats[i]].name; - html += '' + category + ''; + html += `${category}`; } html += '
'; @@ -143,7 +143,7 @@ */ function openTab(args) { open(args.url); - } + }; function slugify(string) { return string.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/--+/g, '-').replace(/(?:^-|-$)/, ''); @@ -151,4 +151,4 @@ getPageContent(); getResponseHeaders(); -})(); +}()); diff --git a/src/drivers/npm/driver.js b/src/drivers/npm/driver.js index 664b4644f..d039c0eed 100644 --- a/src/drivers/npm/driver.js +++ b/src/drivers/npm/driver.js @@ -1,27 +1,79 @@ -'use strict'; -const Wappalyzer = require('./wappalyzer'); + const url = require('url'); const fs = require('fs'); const path = require('path'); const Browser = require('zombie'); +const Wappalyzer = require('./wappalyzer'); -const json = JSON.parse(fs.readFileSync(path.resolve(__dirname + '/apps.json'))); +const json = JSON.parse(fs.readFileSync(path.resolve(`${__dirname}/apps.json`))); const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/; const errorTypes = { - RESPONSE_NOT_OK: 'Response was not ok', - NO_RESPONSE: 'No response from server', - NO_HTML_DOCUMENT: 'No HTML document', + RESPONSE_NOT_OK: 'Response was not ok', + NO_RESPONSE: 'No response from server', + NO_HTML_DOCUMENT: 'No HTML document', }; +function sleep(ms) { + return ms ? new Promise(resolve => setTimeout(resolve, ms)) : Promise.resolve(); +} + +function getHeaders(browser) { + const headers = {}; + + const resource = browser.resources.length + ? browser.resources.filter(_resource => _resource.response).shift() : null; + + if (resource) { + // eslint-disable-next-line no-underscore-dangle + resource.response.headers._headers.forEach((header) => { + if (!headers[header[0]]) { + headers[header[0]] = []; + } + + headers[header[0]].push(header[1]); + }); + } + + return headers; +} + +function getScripts(browser) { + if (!browser.document || !browser.document.scripts) { + return []; + } + + const scripts = Array.prototype.slice + .apply(browser.document.scripts) + .filter(script => script.src) + .map(script => script.src); + + return scripts; +} + +function getCookies(browser) { + const cookies = []; + + if (browser.cookies) { + browser.cookies.forEach(cookie => cookies.push({ + name: cookie.key, + value: cookie.value, + domain: cookie.domain, + path: cookie.path, + })); + } + + return cookies; +} + class Driver { constructor(pageUrl, options) { this.options = Object.assign({}, { - password: '', - proxy: null, - username: '', + password: '', + proxy: null, + username: '', chunkSize: 5, debug: false, delay: 500, @@ -58,46 +110,48 @@ class Driver { this.wappalyzer.driver.log = (message, source, type) => this.log(message, source, type); this.wappalyzer.driver.displayApps = (detected, meta, context) => this.displayApps(detected, meta, context); - process.on('uncaughtException', e => this.wappalyzer.log('Uncaught exception: ' + e.message, 'driver', 'error')); + process.on('uncaughtException', e => this.wappalyzer.log(`Uncaught exception: ${e.message}`, 'driver', 'error')); } analyze() { this.time = { start: new Date().getTime(), last: new Date().getTime(), - } + }; return this.crawl(this.origPageUrl); } log(message, source, type) { - this.options.debug && console.log('[wappalyzer ' + type + ']', '[' + source + ']', message); + if (this.options.debug) { + console.log(`[wappalyzer ${type}]`, `[${source}]`, message); + } } displayApps(detected, meta) { this.meta = meta; - Object.keys(detected).forEach(appName => { + Object.keys(detected).forEach((appName) => { const app = detected[appName]; - var categories = []; + const categories = []; - app.props.cats.forEach(id => { - var category = {}; + app.props.cats.forEach((id) => { + const category = {}; category[id] = json.categories[id].name; - categories.push(category) + categories.push(category); }); - if ( !this.apps.some(detectedApp => detectedApp.name === app.name) ) { + if (!this.apps.some(detectedApp => detectedApp.name === app.name)) { this.apps.push({ name: app.name, confidence: app.confidenceTotal.toString(), version: app.version || null, icon: app.props.icon || 'default.svg', website: app.props.website, - categories + categories, }); } }); @@ -105,55 +159,65 @@ class Driver { fetch(pageUrl, index, depth) { // Return when the URL is a duplicate or maxUrls has been reached - if (this.analyzedPageUrls[pageUrl.href] || this.analyzedPageUrls.length >= this.options.maxUrls) { + if ( + this.analyzedPageUrls[pageUrl.href] + || this.analyzedPageUrls.length >= this.options.maxUrls + ) { return Promise.resolve(); } this.analyzedPageUrls[pageUrl.href] = { - status: 0, - }; + status: 0, + }; const timerScope = { - last: new Date().getTime() + last: new Date().getTime(), }; - this.timer('fetch; url: ' + pageUrl.href + '; depth: ' + depth + '; delay: ' + (this.options.delay * index) + 'ms', timerScope); + this.timer(`fetch; url: ${pageUrl.href}; depth: ${depth}; delay: ${this.options.delay * index}ms`, timerScope); - return new Promise((resolve, reject) => this.sleep(this.options.delay * index).then(() => this.visit(pageUrl, timerScope, resolve, reject))); + return new Promise((resolve, reject) => { + sleep(this.options.delay * index) + .then(() => this.visit(pageUrl, timerScope, resolve, reject)); + }); } visit(pageUrl, timerScope, resolve, reject) { const browser = new Browser({ - proxy: this.options.proxy, + proxy: this.options.proxy, silent: true, strictSSL: false, userAgent: this.options.userAgent, waitDuration: this.options.maxWait, }); - browser.on('authenticate', auth => { - auth.username = this.options.username; - auth.password = this.options.password; - }); + browser.on('authenticate', (auth) => { + auth.username = this.options.username; + auth.password = this.options.password; + }); - this.timer('browser.visit start; url: ' + pageUrl.href, timerScope); + this.timer(`browser.visit start; url: ${pageUrl.href}`, timerScope); browser.visit(pageUrl.href, () => { - this.timer('browser.visit end; url: ' + pageUrl.href, timerScope); + this.timer(`browser.visit end; url: ${pageUrl.href}`, timerScope); + + try { + if (!this.checkResponse(browser, pageUrl)) { + resolve(); - try { - if (!this.checkResponse(browser, pageUrl)) { - return resolve(); - } - } catch(error) { - return reject(error); - } + return; + } + } catch (error) { + reject(error); + + return; + } - const headers = this.getHeaders(browser); + const headers = getHeaders(browser); const html = this.getHtml(browser); - const scripts = this.getScripts(browser); + const scripts = getScripts(browser); const js = this.getJs(browser); - const cookies = this.getCookies(browser); + const cookies = getCookies(browser); this.wappalyzer.analyze(pageUrl, { headers, @@ -165,14 +229,14 @@ class Driver { .then(() => { const links = Array.prototype.reduce.call( browser.document.getElementsByTagName('a'), (results, link) => { - if ( link.protocol.match(/https?:/) && link.hostname === this.origPageUrl.hostname && extensions.test(link.pathname) ) { + if (link.protocol.match(/https?:/) && link.hostname === this.origPageUrl.hostname && extensions.test(link.pathname)) { link.hash = ''; results.push(url.parse(link.href)); } return results; - }, [] + }, [], ); return resolve(links); @@ -182,25 +246,26 @@ class Driver { checkResponse(browser, pageUrl) { // Validate response - const resource = browser.resources.length ? browser.resources.filter(resource => resource.response).shift() : null; + const resource = browser.resources.length + ? browser.resources.filter(_resource => _resource.response).shift() : null; - if ( !resource ) { + if (!resource) { throw new Error('NO_RESPONSE'); } - this.analyzedPageUrls[pageUrl.href].status = resource.response.status; + this.analyzedPageUrls[pageUrl.href].status = resource.response.status; - if ( resource.response.status !== 200 ) { + if (resource.response.status !== 200) { throw new Error('RESPONSE_NOT_OK'); } - const headers = this.getHeaders(browser); + const headers = getHeaders(browser); // Validate content type - const contentType = headers.hasOwnProperty('content-type') ? headers['content-type'].shift() : null; + const contentType = headers['content-type'] ? headers['content-type'].shift() : null; - if ( !contentType || !/\btext\/html\b/.test(contentType) ) { - this.wappalyzer.log('Skipping; url: ' + pageUrl.href + '; content type: ' + contentType, 'driver'); + if (!contentType || !/\btext\/html\b/.test(contentType)) { + this.wappalyzer.log(`Skipping; url: ${pageUrl.href}; content type: ${contentType}`, 'driver'); delete this.analyzedPageUrls[pageUrl.href]; @@ -208,80 +273,50 @@ class Driver { } // Validate document - if ( !browser.document || !browser.document.documentElement ) { + if (!browser.document || !browser.document.documentElement) { throw new Error('NO_HTML_DOCUMENT'); } return true; } - getHeaders(browser) { - const headers = {}; - - const resource = browser.resources.length ? browser.resources.filter(resource => resource.response).shift() : null; - - if ( resource ) { - resource.response.headers._headers.forEach(header => { - if ( !headers[header[0]] ){ - headers[header[0]] = []; - } - - headers[header[0]].push(header[1]); - }); - } - - return headers; - } - getHtml(browser) { let html = ''; try { html = browser.html() .split('\n') - .slice(0, this.options.htmlMaxRows / 2).concat(html.slice(html.length - this.options.htmlMaxRows / 2)) + .slice(0, this.options.htmlMaxRows / 2) + .concat(html.slice(html.length - this.options.htmlMaxRows / 2)) .map(line => line.substring(0, this.options.htmlMaxCols)) .join('\n'); - } catch(error) { + } catch (error) { this.wappalyzer.log(error.message, 'browser', 'error'); } return html; } - getScripts(browser) { - if ( !browser.document || !browser.document.scripts ) { - return []; - } - - const scripts = Array.prototype.slice - .apply(browser.document.scripts) - .filter(script => script.src) - .map(script => script.src); - - return scripts; - } - getJs(browser) { const patterns = this.wappalyzer.jsPatterns; const js = {}; - Object.keys(patterns).forEach(appName => { + Object.keys(patterns).forEach((appName) => { js[appName] = {}; - Object.keys(patterns[appName]).forEach(chain => { + Object.keys(patterns[appName]).forEach((chain) => { js[appName][chain] = {}; patterns[appName][chain].forEach((pattern, index) => { const properties = chain.split('.'); - let value = properties.reduce((parent, property) => { - return parent && parent.hasOwnProperty(property) ? parent[property] : null; - }, browser.window); + let value = properties + .reduce((parent, property) => (parent && parent[property] + ? parent[property] : null), browser.window); value = typeof value === 'string' || typeof value === 'number' ? value : !!value; - if ( value ) { + if (value) { js[appName][chain][index] = value; } }); @@ -291,81 +326,61 @@ class Driver { return js; } - getCookies(browser) { - const cookies = []; - - if ( browser.cookies ) { - browser.cookies.forEach(cookie => cookies.push({ - name: cookie.key, - value: cookie.value, - domain: cookie.domain, - path: cookie.path, - })); - } - - return cookies; - } - crawl(pageUrl, index = 1, depth = 1) { - pageUrl.canonical = pageUrl.protocol + '//' + pageUrl.host + pageUrl.pathname; + pageUrl.canonical = `${pageUrl.protocol}//${pageUrl.host}${pageUrl.pathname}`; - return new Promise(resolve => { + return new Promise((resolve) => { this.fetch(pageUrl, index, depth) - .catch(error => { - const type = error.message && errorTypes[error.message] ? error.message : 'UNKNOWN_ERROR'; - const message = error.message && errorTypes[error.message] ? errorTypes[error.message] : 'Unknown error'; - - this.analyzedPageUrls[pageUrl.href].error = { - type, - message, - }; - - this.wappalyzer.log(`${message}; url: ${pageUrl.href}`, 'driver', 'error'); - }) - .then(links => { - if ( links && this.options.recursive && depth < this.options.maxDepth ) { + .catch((error) => { + const type = error.message && errorTypes[error.message] ? error.message : 'UNKNOWN_ERROR'; + const message = error.message && errorTypes[error.message] ? errorTypes[error.message] : 'Unknown error'; + + this.analyzedPageUrls[pageUrl.href].error = { + type, + message, + }; + + this.wappalyzer.log(`${message}; url: ${pageUrl.href}`, 'driver', 'error'); + }) + .then((links) => { + if (links && this.options.recursive && depth < this.options.maxDepth) { return this.chunk(links.slice(0, this.options.maxUrls), depth + 1); - } else { - return Promise.resolve(); } + return Promise.resolve(); }) .then(() => { resolve({ urls: this.analyzedPageUrls, applications: this.apps, - meta: this.meta + meta: this.meta, }); }); }); } chunk(links, depth, chunk = 0) { - if ( links.length === 0 ) { + if (links.length === 0) { return Promise.resolve(); } const chunked = links.splice(0, this.options.chunkSize); - return new Promise(resolve => { + return new Promise((resolve) => { Promise.all(chunked.map((link, index) => this.crawl(link, index, depth))) .then(() => this.chunk(links, depth, chunk + 1)) .then(() => resolve()); }); } - sleep(ms) { - return ms ? new Promise(resolve => setTimeout(resolve, ms)) : Promise.resolve(); - } - timer(message, scope) { const time = new Date().getTime(); - const sinceStart = ( Math.round(( time - this.time.start ) / 10) / 100) + 's'; - const sinceLast = ( Math.round(( time - scope.last ) / 10) / 100) + 's'; + const sinceStart = `${Math.round((time - this.time.start) / 10) / 100}s`; + const sinceLast = `${Math.round((time - scope.last) / 10) / 100}s`; - this.wappalyzer.log('[timer] ' + message + '; lapsed: ' + sinceLast + ' / ' + sinceStart, 'driver'); + this.wappalyzer.log(`[timer] ${message}; lapsed: ${sinceLast} / ${sinceStart}`, 'driver'); scope.last = time; } -}; +} module.exports = Driver; diff --git a/src/drivers/npm/index.js b/src/drivers/npm/index.js index a638e2aea..cdff2ba3d 100755 --- a/src/drivers/npm/index.js +++ b/src/drivers/npm/index.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -'use strict'; + const Wappalyzer = require('./driver'); @@ -7,36 +7,39 @@ const args = process.argv.slice(2); const url = args.shift() || ''; -if ( !url ) { +if (!url) { process.stderr.write('No URL specified\n'); process.exit(1); } -let options = {}; +const options = {}; + let arg; -while ( arg = args.shift() ) { - let matches = /--([^=]+)=(.+)/.exec(arg); +do { + arg = args.shift(); - if ( matches ) { - let key = matches[1].replace(/-\w/g, matches => matches[1].toUpperCase()); - let value = matches[2]; + const matches = /--([^=]+)=(.+)/.exec(arg); + + if (matches) { + const key = matches[1].replace(/-\w/g, _matches => _matches[1].toUpperCase()); + const value = matches[2]; options[key] = value; } -} +} while (arg); const wappalyzer = new Wappalyzer(url, options); wappalyzer.analyze() - .then(json => { - process.stdout.write(JSON.stringify(json) + '\n') + .then((json) => { + process.stdout.write(`${JSON.stringify(json)}\n`); process.exit(0); }) - .catch(error => { - process.stderr.write(error + '\n') + .catch((error) => { + process.stderr.write(`${error}\n`); process.exit(1); }); diff --git a/src/drivers/webextension/html/background.html b/src/drivers/webextension/html/background.html index 1c31887fc..8a03c7a9d 100644 --- a/src/drivers/webextension/html/background.html +++ b/src/drivers/webextension/html/background.html @@ -7,7 +7,7 @@ - + diff --git a/src/drivers/webextension/html/popup.html b/src/drivers/webextension/html/popup.html index 9fc0278ad..b453b70c3 100644 --- a/src/drivers/webextension/html/popup.html +++ b/src/drivers/webextension/html/popup.html @@ -7,7 +7,7 @@ - + diff --git a/src/drivers/webextension/js/content.js b/src/drivers/webextension/js/content.js index be07c97c0..4d29c981a 100644 --- a/src/drivers/webextension/js/content.js +++ b/src/drivers/webextension/js/content.js @@ -1,12 +1,12 @@ /** global: browser */ /** global: XMLSerializer */ -if ( typeof browser !== 'undefined' && typeof document.body !== 'undefined' ) { +if (typeof browser !== 'undefined' && typeof document.body !== 'undefined') { try { sendMessage('init', {}); // HTML - var html = new XMLSerializer().serializeToString(document).split('\n'); + let html = new XMLSerializer().serializeToString(document).split('\n'); html = html .slice(0, 1000).concat(html.slice(html.length - 1000)) @@ -26,7 +26,7 @@ if ( typeof browser !== 'undefined' && typeof document.body !== 'undefined' ) { const script = document.createElement('script'); script.onload = () => { - const onMessage = event => { + const onMessage = (event) => { if (event.data.id !== 'js') { return; } @@ -40,11 +40,11 @@ if ( typeof browser !== 'undefined' && typeof document.body !== 'undefined' ) { addEventListener('message', onMessage); - sendMessage('get_js_patterns', {}, response => { + sendMessage('get_js_patterns', {}, (response) => { if (response) { postMessage({ id: 'patterns', - patterns: response.patterns + patterns: response.patterns, }, '*'); } }); @@ -62,8 +62,8 @@ function sendMessage(id, subject, callback) { (chrome || browser).runtime.sendMessage({ id, subject, - source: 'content.js' - }, callback || ( () => {} )); + source: 'content.js', + }, callback || (() => {})); } // https://stackoverflow.com/a/44774834 diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js index 988d53d12..6f339da05 100644 --- a/src/drivers/webextension/js/driver.js +++ b/src/drivers/webextension/js/driver.js @@ -2,17 +2,22 @@ * WebExtension driver */ +/* eslint-env browser */ +/* global browser, chrome, fetch, Wappalyzer */ + /** global: browser */ +/** global: chrome */ +/** global: fetch */ /** global: Wappalyzer */ const wappalyzer = new Wappalyzer(); -var tabCache = {}; -var categoryOrder = []; -var options = {}; -var robotsTxtQueue = {}; +const tabCache = {}; +let categoryOrder = []; +const options = {}; +const robotsTxtQueue = {}; -browser.tabs.onRemoved.addListener(tabId => { +browser.tabs.onRemoved.addListener((tabId) => { tabCache[tabId] = null; }); @@ -21,19 +26,19 @@ browser.tabs.onRemoved.addListener(tabId => { */ function getOption(name, defaultValue = null) { return new Promise((resolve, reject) => { - const callback = item => { - options[name] = item.hasOwnProperty(name) ? item[name] : defaultValue; + const callback = (item) => { + options[name] = item[name] ? item[name] : defaultValue; resolve(options[name]); }; browser.storage.local.get(name) .then(callback) - .catch(error => { - wappalyzer.log(error, 'driver', 'error') + .catch((error) => { + wappalyzer.log(error, 'driver', 'error'); reject(); - }); + }); }); } @@ -56,7 +61,7 @@ function setOption(name, value) { function openTab(args) { browser.tabs.create({ url: args.url, - active: args.background === undefined || !args.background + active: args.background === undefined || !args.background, }); } @@ -66,7 +71,7 @@ function openTab(args) { function post(url, body) { fetch(url, { method: 'POST', - body: JSON.stringify(body) + body: JSON.stringify(body), }) .then(response => wappalyzer.log(`POST ${url}: ${response.status}`, 'driver')) .catch(error => wappalyzer.log(`POST ${url}: ${error}`, 'driver', 'error')); @@ -74,7 +79,7 @@ function post(url, body) { fetch('../apps.json') .then(response => response.json()) - .then(json => { + .then((json) => { wappalyzer.apps = json.apps; wappalyzer.categories = json.categories; @@ -87,21 +92,21 @@ fetch('../apps.json') .catch(error => wappalyzer.log(`GET apps.json: ${error}`, 'driver', 'error')); // Version check -let version = browser.runtime.getManifest().version; +const { version } = browser.runtime.getManifest(); getOption('version') - .then(previousVersion => { + .then((previousVersion) => { if (previousVersion === null) { openTab({ - url: wappalyzer.config.websiteURL + 'installed' + url: `${wappalyzer.config.websiteURL}installed`, }); } else if (version !== previousVersion) { getOption('upgradeMessage', true) - .then(upgradeMessage => { + .then((upgradeMessage) => { if (upgradeMessage) { openTab({ - url: wappalyzer.config.websiteURL + 'upgraded?v' + version, - background: true + url: `${wappalyzer.config.websiteURL}upgraded?v${version}`, + background: true, }); } }); @@ -113,32 +118,37 @@ getOption('version') getOption('dynamicIcon', true); getOption('pinnedCategory'); -getOption('hostnameCache', {}).then(hostnameCache => wappalyzer.hostnameCache = hostnameCache); +getOption('hostnameCache', {}) + .then((hostnameCache) => { + wappalyzer.hostnameCache = hostnameCache; + + return hostnameCache; + }); // Run content script on all tabs -browser.tabs.query({ url: [ 'http://*/*', 'https://*/*' ] }) - .then(tabs => { - tabs.forEach(tab => { +browser.tabs.query({ url: ['http://*/*', 'https://*/*'] }) + .then((tabs) => { + tabs.forEach((tab) => { browser.tabs.executeScript(tab.id, { - file: '../js/content.js' + file: '../js/content.js', }); - }) + }); }) .catch(error => wappalyzer.log(error, 'driver', 'error')); // Capture response headers -browser.webRequest.onCompleted.addListener(request => { +browser.webRequest.onCompleted.addListener((request) => { const headers = {}; if (request.responseHeaders) { const url = wappalyzer.parseUrl(request.url); - browser.tabs.query({ url: [ url.href ] }) - .then(tabs => { + browser.tabs.query({ url: [url.href] }) + .then((tabs) => { const tab = tabs[0] || null; if (tab) { - request.responseHeaders.forEach(header => { + request.responseHeaders.forEach((header) => { const name = header.name.toLowerCase(); headers[name] = headers[name] || []; @@ -153,32 +163,32 @@ browser.webRequest.onCompleted.addListener(request => { }) .catch(error => wappalyzer.log(error, 'driver', 'error')); } -}, { urls: [ 'http://*/*', 'https://*/*' ], types: [ 'main_frame' ] }, [ 'responseHeaders' ]); +}, { urls: ['http://*/*', 'https://*/*'], types: ['main_frame'] }, ['responseHeaders']); // Listen for messages (chrome || browser).runtime.onMessage.addListener((message, sender, sendResponse) => { - if (typeof message.id != 'undefined') { + if (typeof message.id !== 'undefined') { if (message.id !== 'log') { - wappalyzer.log('Message' + (message.source ? ' from ' + message.source : '') + ': ' + message.id, 'driver'); + wappalyzer.log(`Message${message.source ? ` from ${message.source}` : ''}: ${message.id}`, 'driver'); } - let url = wappalyzer.parseUrl(sender.tab ? sender.tab.url : ''); + const url = wappalyzer.parseUrl(sender.tab ? sender.tab.url : ''); let response; - switch ( message.id ) { + switch (message.id) { case 'log': wappalyzer.log(message.subject, message.source); break; case 'init': - browser.cookies.getAll({ domain: '.' + url.hostname }) + browser.cookies.getAll({ domain: `.${url.hostname}` }) .then(cookies => wappalyzer.analyze(url, { cookies }, { tab: sender.tab })); break; case 'analyze': wappalyzer.analyze(url, message.subject, { tab: sender.tab }); - setOption('hostnameCache', wappalyzer.hostnameCache); + setOption('hostnameCache', wappalyzer.hostnameCache); break; case 'ad_log': @@ -200,7 +210,7 @@ browser.webRequest.onCompleted.addListener(request => { break; case 'get_js_patterns': response = { - patterns: wappalyzer.jsPatterns + patterns: wappalyzer.jsPatterns, }; break; @@ -226,14 +236,14 @@ wappalyzer.driver.log = (message, source, type) => { * Display apps */ wappalyzer.driver.displayApps = (detected, meta, context) => { - let tab = context.tab; + const { tab } = context; if (tab === undefined) { return; } tabCache[tab.id] = tabCache[tab.id] || { - detected: [] + detected: [], }; tabCache[tab.id].detected = detected; @@ -241,11 +251,11 @@ wappalyzer.driver.displayApps = (detected, meta, context) => { let found = false; // Find the main application to display - [ options.pinnedCategory ].concat(categoryOrder).forEach(match => { - Object.keys(detected).forEach(appName => { - let app = detected[appName]; + [options.pinnedCategory].concat(categoryOrder).forEach((match) => { + Object.keys(detected).forEach((appName) => { + const app = detected[appName]; - app.props.cats.forEach(category => { + app.props.cats.forEach((category) => { if (category === match && !found) { let icon = app.props.icon || 'default.svg'; @@ -254,15 +264,15 @@ wappalyzer.driver.displayApps = (detected, meta, context) => { } if (/\.svg$/i.test(icon)) { - icon = 'converted/' + icon.replace(/\.svg$/, '.png'); + icon = `converted/${icon.replace(/\.svg$/, '.png')}`; } try { browser.pageAction.setIcon({ tabId: tab.id, - path: '../images/icons/' + icon + path: `../images/icons/${icon}`, }); - } catch(e) { + } catch (e) { // Firefox for Android does not support setIcon see https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 } @@ -284,35 +294,39 @@ wappalyzer.driver.displayApps = (detected, meta, context) => { * Fetch and cache robots.txt for host */ wappalyzer.driver.getRobotsTxt = (host, secure = false) => { - if (robotsTxtQueue.hasOwnProperty(host)) { + if (robotsTxtQueue[host]) { return robotsTxtQueue[host]; } - robotsTxtQueue[host] = new Promise(resolve => { + robotsTxtQueue[host] = new Promise((resolve) => { getOption('tracking', true) - .then(tracking => { + .then((tracking) => { if (!tracking) { - return resolve([]); + resolve([]); + + return; } getOption('robotsTxtCache') - .then(robotsTxtCache => { + .then((robotsTxtCache) => { robotsTxtCache = robotsTxtCache || {}; - if ( host in robotsTxtCache ) { - return resolve(robotsTxtCache[host]); + if (host in robotsTxtCache) { + resolve(robotsTxtCache[host]); + + return; } const timeout = setTimeout(() => resolve([]), 3000); - fetch('http' + (secure ? 's' : '') + '://' + host + '/robots.txt', { redirect: 'follow' }) - .then(response => { + fetch(`http${secure ? 's' : ''}://${host}/robots.txt`, { redirect: 'follow' }) + .then((response) => { clearTimeout(timeout); return response.ok ? response.text() : ''; }) - .then(robotsTxt => { - robotsTxtCache[host] = wappalyzer.parseRobotsTxt(robotsTxt); + .then((robotsTxt) => { + robotsTxtCache[host] = Wappalyzer.parseRobotsTxt(robotsTxt); setOption('robotsTxtCache', robotsTxtCache); @@ -322,7 +336,7 @@ wappalyzer.driver.getRobotsTxt = (host, secure = false) => { }); }); }) - .finally(() => delete robotsTxtQueue[host]); + .finally(() => delete robotsTxtQueue[host]); return robotsTxtQueue[host]; }; @@ -332,7 +346,7 @@ wappalyzer.driver.getRobotsTxt = (host, secure = false) => { */ wappalyzer.driver.ping = (hostnameCache = {}, adCache = []) => { getOption('tracking', true) - .then(tracking => { + .then((tracking) => { if (tracking) { if (Object.keys(hostnameCache).length) { post('https://api.wappalyzer.com/ping/v1/', hostnameCache); diff --git a/src/drivers/webextension/js/iframe.js b/src/drivers/webextension/js/iframe.js deleted file mode 100644 index 883ebea71..000000000 --- a/src/drivers/webextension/js/iframe.js +++ /dev/null @@ -1,1185 +0,0 @@ -'use strict'; - -(function(win) { - -var exports = {}; - -(function(exports) { - - var utils = { - normalizeUrl: function(url) { - - return this.hashUrl(url) || null; - - }, - - getReferrer: function() { - return this.normalizeUrl(document.referrer); - }, - - getPageUrl: function() { - return this.normalizeUrl(window.location.href); - }, - hashUrl: function(url) { - var a, - result; - - if ( !url || url.indexOf('http') !== 0 ) { - return null; - } - - a = document.createElement('a'); - a.href = url; - - result = a.protocol + '//' + a.hostname + '/'; - - if ( a.pathname && a.pathname !== '/' ) { - result += this.hashCode(a.pathname); - } - - if ( a.search ) { - result += '?' + this.hashCode(a.search); - } - - if ( a.hash ) { - result += '#' + this.hashCode(a.hash); - } - - return result; - }, - - hashCode: function(str) { - var hash = 0, - kar, - i; - - if ( str.length === 0 ) { - return hash; - } - - for ( i = 0; i < str.length; i++ ) { - kar = str.charCodeAt(i); - hash = ((hash << 5) - hash) + kar; - hash = hash & hash; - } - - return hash + Math.pow(2, 32); - }, - - realArray: function(a) { - return Array.prototype.slice.apply(a); - }, - - onDocLoaded: function(doc, callback) { - if ( doc.readyState === 'loading' ) { - doc.addEventListener('DOMContentLoaded', callback); - } else { - callback(); - } - }, - - SCRIPT_IN_WINDOW_TOP: window === window.top, - - isFriendlyWindow: function(win) { - - var href; - try { - href = win.location.href; - } catch(e) { - return false; - } - return true; - }, - - elementWindow: function(el) { - return el.ownerDocument.defaultView; - }, - - viewport: function(win) { - return {width: win.innerWidth, height: win.innerHeight}; - }, - - parseQS: function(qs) { - if ( qs.indexOf('http') === 0 ) { - qs = qs.split('?')[1]; - } - var i, kvs, key, val; - var dict = {}; - qs = qs.split('&'); - for ( i = 0; i < qs.length; i++ ) { - kvs = qs[i].split('='); - key = kvs[0]; - val = kvs.slice(1).join('='); - try { - dict[key] = window.decodeURIComponent(val); - } catch (e) { - - continue; - } - } - return dict; - }, - sendToBackground: function(message, event, responseMessage, onResponse) { - if ( typeof browser !== 'undefined' ) { - var response = browser.runtime.sendMessage(message); - response.then(onResponse); - } else if ( typeof chrome !== 'undefined' ) { - chrome.runtime.sendMessage(message, onResponse); - } else if ( window.self.port ) { - window.self.port.on(responseMessage, onResponse); - window.self.port.emit(event, message); - } - }, - - askIfTrackingEnabled: function(callback, elseCallback) { - - this.sendToBackground( - 'is_tracking_enabled', - '', - 'tracking_enabled_response', - function(message) { - if ( message && message.tracking_enabled ) { - - callback(); - } else { - - elseCallback(); - } - } - ); - - } - }; - - utils.SCRIPT_IN_FRIENDLY_IFRAME = !utils.SCRIPT_IN_WINDOW_TOP && utils.isFriendlyWindow(window.parent); - utils.SCRIPT_IN_HOSTILE_IFRAME = !utils.SCRIPT_IN_WINDOW_TOP && !utils.SCRIPT_IN_FRIENDLY_IFRAME; - - function LogGenerator() { - this.msgNum = 0; - this.pageMeta = { - 'url': utils.getPageUrl(), - 'isHP': window.location.pathname === '/', - 'referrer': utils.getReferrer(), - 'rand': Math.floor(Math.random() * 10e12), - 'startTime': new Date().getTime() - }; - } - - LogGenerator.prototype = { - log: function(event, opt_assets, opt_pageTags) { - var opt_video_assets; - if ( event === 'video' || event === 'invalid-video' ) { - opt_video_assets = opt_assets || []; - opt_assets = []; - } else { - opt_video_assets = []; - opt_assets = opt_assets || []; - } - var result = { - doc: this.pageMeta, - event: event, - video_assets: opt_video_assets, - assets: opt_assets, - version: '3', - mrev: '4aeaa5a-c', - msgNum: this.msgNum, - timestamp: new Date().getTime(), - pageVis: document.visibilityState, - pageFoc: document.hasFocus(), - pageTags: opt_pageTags || [] - }; - this.msgNum++; - return result; - } - }; - - utils.LogGenerator = LogGenerator; - - exports.utils = utils; -})(exports); - -(function(exports) { - - var SizeMatcher = { - VALID_AD_SIZES: [ - [300, 50], - [320, 50], - [160, 600], - [300, 250], - [300, 600], - [300, 1050], - [336, 280], - [336, 850], - [468, 60], - [728, 90], - [728, 250], - [728, 270], - [970, 66], - [970, 90], - [970, 125], - [970, 250], - [970, 400], - [970, 415], - [1280, 100] - ], - - PX_SIZE_TOL: 10, - - getMatchedAdSize: function(width, height) { - - if ( !this.set ) { - this.set = this._makeSizeSet(); - } - - return this.set[Math.round(width) + 'x' + Math.round(height)]; - }, - - elementIsAdShaped: function(el) { - return !!this.getMatchedAdSizeForElement(el); - }, - - getMatchedAdSizeForElement: function(el) { - var rect = el.getBoundingClientRect(); - return this.getMatchedAdSize(rect.width, rect.height); - }, - - _makeSizeSet: function() { - var set = {}; - var i; - var xfuz; - var yfuz; - var size; - var width; - var height; - - for ( i = 0; i < this.VALID_AD_SIZES.length; i++ ) { - for ( xfuz = -this.PX_SIZE_TOL; xfuz <= this.PX_SIZE_TOL; xfuz++ ) { - for ( yfuz = -this.PX_SIZE_TOL; yfuz <= this.PX_SIZE_TOL; yfuz++ ) { - size = this.VALID_AD_SIZES[i]; - width = size[0] + xfuz; - height = size[1] + yfuz; - set[width + 'x' + height] = size; - } - } - } - return set; - } - }; - - var Throttler = { - MAX_SEARCHES_PER_WINDOW: 10, - MAX_SEARCHES_PER_ELEMENT: 2, - - countSearch: function(el) { - if ( typeof el.searches !== 'number' ) { - el.searches = 0; - } - - el.searches += 1; - }, - - throttle: function(el, max) { - if ( typeof el.searches === 'number' && el.searches >= max ) { - return true; - } - return false; - }, - - throttleElement: function(el) { - return this.throttle(el, this.MAX_SEARCHES_PER_ELEMENT); - }, - - throttleWin: function(win) { - return this.throttle(win, this.MAX_SEARCHES_PER_WINDOW); - }, - - getCount: function(el) { - return el.searches || 0; - } - }; - - function TopSearcher(win) { - this.win = win; - this.doc = win.document; - } - - TopSearcher.prototype.search = function() { - var candidates = exports.utils.realArray(this.doc.querySelectorAll('img, object, embed')), - html5Ad, - ads = []; - - ads = ads.concat(candidates.filter(function(el) { - if ( !el.mpAdFound && !Throttler.throttleElement(el) ) { - Throttler.countSearch(el); - if ( (el.tagName !== 'IMG' || isStandardImage(el)) && SizeMatcher.elementIsAdShaped(el) ) { - el.mpAdFound = true; - return true; - } - } - return false; - })); - - html5Ad = this._mainGetHTMLAd(); - if ( html5Ad ) { - html5Ad.html5 = true; - html5Ad.mpAdFound = true; - ads.push(html5Ad); - } - - return ads; - }; - - TopSearcher.prototype._mainGetHTMLAd = function() { - var styles = this.doc.querySelectorAll('div > style, div > link[rel="stylesheet"]'), - i, div; - for ( i = 0; i < styles.length; i++ ) { - div = styles[i].parentNode; - if ( !div.mpAdFound && SizeMatcher.elementIsAdShaped(div) && this._jumpedOut(div) ) { - return div; - } - } - }; - - TopSearcher.prototype._jumpedOut = function(el) { - var siblings, ifrs; - siblings = exports.utils.realArray(el.parentNode.children); - ifrs = siblings.filter(function(el) { - return el.tagName === 'IFRAME' && el.offsetWidth === 0 && el.offsetHeight === 0; - }); - return ifrs.length > 0; - }; - - function IframeSearcher(win) { - this.MIN_AD_AREA = 14000; - this.MIN_WINDOW_PX = 10; - - this.win = win; - this.doc = win.document; - this.body = win.document.body; - this.winClickTag = win.clickTag; - this.adSizeMeta = this._getAdSizeMeta(); - this.numElementsInBody = (this.body && this.body.querySelectorAll('*').length) || 0; - - this.shouldSearchWindow = false; - if ( !this.win.mpAdFound && this.body && !Throttler.throttleWin(this.win) ) { - this.winWidth = this.win.innerWidth; - this.winHeight = this.win.innerHeight; - if ( this._meetsMinAdSize(this.winWidth, this.winHeight) && !this._containsLargeIframes() ) { - this.shouldSearchWindow = true; - } - } - - } - - IframeSearcher.prototype.search = function() { - var ad; - - if ( this.shouldSearchWindow ) { - ad = this._search(); - if ( ad ) { - ad.mpAdFound = true; - win.mpAdFound = true; - return ad; - } - Throttler.countSearch(this.win); - } - - return null; - }; - - IframeSearcher.prototype._search = function() { - var _this = this, - stdCandidates, - html5Candidates, - stdEl, - html5El; - - stdCandidates = this.body.querySelectorAll('img, object, embed'); - - stdEl = getFirst(stdCandidates, function(el) { - if ( !el.mpAdFound && - !Throttler.throttleElement(el) && - (el.tagName !== 'IMG' || isStandardImage(el)) && - _this._elementIsAtLeastAsBigAsWindow(el)) - { - return true; - } - Throttler.countSearch(el); - return false; - }); - - if ( stdEl ) { - return stdEl; - } - - if ( this._isHTML5Iframe() ) { - html5Candidates = this.doc.querySelectorAll('body, canvas, button, video, svg, div'); - html5El = getFirst(html5Candidates, function(el) { - - if ( _this._elementIsAtLeastAsBigAsWindow(el) ) { - return true; - } - Throttler.countSearch(el); - return false; - }); - } - - if ( html5El ) { - html5El.html5 = true; - html5El.winClickTag = this.winClickTag; - html5El.adSizeMeta = this.adSizeMeta; - return html5El; - } - - return null; - }; - - IframeSearcher.prototype._isHTML5Iframe = function() { - if ( this.winClickTag || this.adSizeMeta ) { - return true; - } - - if ( this.doc.querySelectorAll('canvas', 'button', 'video', 'svg').length > 0 ) { - return true; - } - - if ( this.numElementsInBody >= 5 && Throttler.getCount(this.win) > 0 && this.doc.querySelectorAll('div').length > 0 ) { - return true; - } - - return false; - }; - - IframeSearcher.prototype._elementIsAtLeastAsBigAsWindow = function(el) { - var rect = el.getBoundingClientRect(), - tol = 0.95; - - return rect.width >= (tol * this.winWidth) && rect.height >= (tol * this.winHeight); - }; - - IframeSearcher.prototype._meetsMinAdSize = function(width, height) { - return (width * height) >= this.MIN_AD_AREA; - }; - - IframeSearcher.prototype._containsLargeIframes = function() { - var iframes = this.doc.querySelectorAll('iframe'); - var rect; - var i; - for ( i = 0; i < iframes.length; i++ ) { - rect = iframes[i].getBoundingClientRect(); - if ( rect.width > this.MIN_WINDOW_PX || rect.height > this.MIN_WINDOW_PX ) { - return true; - } - } - return false; - }; - - IframeSearcher.prototype._getAdSizeMeta = function() { - var adSizeMeta = this.doc.querySelectorAll('meta[name="ad.size"]'); - if ( adSizeMeta.length > 0 ) { - return adSizeMeta[0].content; - } else { - return null; - } - }; - - function getFirst(arr, testFn) { - var i, el; - for ( i = 0; i < arr.length; i++ ) { - el = arr[i]; - if ( testFn(el) ) { - return el; - } - } - return null; - } - - function isStandardImage(img) { - - return img.src && (img.parentNode.tagName === 'A' || img.getAttribute('onclick')); - } - - function getFriendlyIframes(win) { - var iframes = win.document.querySelectorAll('iframe'); - iframes = exports.utils.realArray(iframes); - var friendlyIframes = iframes.filter(function(ifr) { - return exports.utils.isFriendlyWindow(ifr.contentWindow); - }); - return friendlyIframes; - } - - function findAds(win) { - var i, - iframes, - searcher, - ad, - ads = []; - - if ( win === win.top ) { - searcher = new TopSearcher(win); - ads = ads.concat(searcher.search()); - } else { - searcher = new IframeSearcher(win); - ad = searcher.search(); - if ( ad ) { - ads.push(ad); - } - } - - iframes = getFriendlyIframes(win); - for ( i = 0; i < iframes.length; i++ ) { - ads = ads.concat(findAds(iframes[i].contentWindow)); - } - - return ads; - } - - exports.adfinder = { - getMatchedAdSize: SizeMatcher.getMatchedAdSize.bind(SizeMatcher), - findAds: findAds - }; -})(exports); - -(function(exports) { - - var parser = { - TAGS_WITH_SRC_ATTR: { - 'IMG': true, - 'SCRIPT': true, - 'IFRAME': true, - 'EMBED': true - }, - - MAX_ATTR_LEN: 100, - - getUrl: function(el, params) { - var url; - - if ( this.TAGS_WITH_SRC_ATTR.hasOwnProperty(el.tagName) ) { - url = el.src; - - } else if ( el.tagName === 'OBJECT' ) { - url = el.data || (params && params.movie) || null; - - } else if ( el.tagName === 'A' ) { - url = el.href; - } - - if ( url && url.indexOf('http') === 0 ) { - return url; - } else { - return null; - } - }, - - getParams: function(el) { - if ( el.tagName !== 'OBJECT' ) { - return null; - } - - var i, child; - var params = {}; - var children = el.children; - for ( i = 0; i < children.length; i++ ) { - child = children[i]; - if ( child.tagName === 'PARAM' && child.name ) { - - params[child.name.toLowerCase()] = child.value; - } - } - return params; - }, - - getPosition: function(el) { - var rect = el.getBoundingClientRect(); - var win = exports.utils.elementWindow(el); - - return { - width: Math.round(rect.width), - height: Math.round(rect.height), - left: Math.round(rect.left + win.pageXOffset), - top: Math.round(rect.top + win.pageYOffset) - }; - }, - - getFlashvars: function(el, params, url) { - var flashvars; - var urlQS = url && url.split('?')[1]; - - if ( el.tagName === 'EMBED' ) { - flashvars = el.getAttribute('flashvars') || urlQS; - - } else if ( el.tagName === 'OBJECT' ) { - flashvars = params.flashvars || el.getAttribute('flashvars') || urlQS; - } - - return (flashvars && exports.utils.parseQS(flashvars)) || null; - }, - - findClickThru: function(el, flashvars) { - var key; - if ( el.tagName === 'IMG' && el.parentElement.tagName === 'A' ) { - return el.parentElement.href; - } else if ( flashvars ) { - for ( key in flashvars ) { - if ( flashvars.hasOwnProperty(key) ) { - - if ( key.toLowerCase().indexOf('clicktag') === 0 ) { - return flashvars[key]; - } - } - } - } - return null; - }, - - getAttr: function(el, name) { - var val = el.getAttribute(name); - - if ( val && val.slice && val.toString ) { - - return val.slice(0, this.MAX_ATTR_LEN).toString(); - } else { - return null; - } - }, - - putPropIfExists: function(obj, name, val) { - if ( val ) { - obj[name] = val; - } - }, - - putAttrIfExists: function(obj, el, name) { - var val = this.getAttr(el, name); - this.putPropIfExists(obj, name, val); - }, - - elementToJSON: function(el, opt_findClickThru) { - var pos = this.getPosition(el); - var params = this.getParams(el); - var url = this.getUrl(el, params); - var flashvars = this.getFlashvars(el, params, url); - var clickThru = opt_findClickThru && this.findClickThru(el, flashvars); - var json = { - tagName: el.tagName, - width: pos.width, - height: pos.height, - left: pos.left, - top: pos.top, - children: [] - }; - - if ( params ) { - - delete params.flashvars; - } - - this.putAttrIfExists(json, el, 'id'); - this.putAttrIfExists(json, el, 'class'); - this.putAttrIfExists(json, el, 'name'); - - this.putPropIfExists(json, 'flashvars', flashvars); - this.putPropIfExists(json, 'url', url); - this.putPropIfExists(json, 'params', params); - this.putPropIfExists(json, 'clickThru', clickThru); - - return json; - } - }; - - exports.parser = { elementToJSON: parser.elementToJSON.bind(parser) }; -})(exports); - -(function(exports) { - - var ContextManager = function(adData) { - this.adData = adData; - }; - - ContextManager.prototype = { - CONTAINER_SIZE_TOL: 0.4, - ASPECT_RATIO_FOR_LEADERBOARDS: 2, - - isValidContainer: function(el, opt_curWin) { - - var cWidth = el.clientWidth; - var cHeight = el.clientHeight; - - var adWidth = this.adData.width; - var adHeight = this.adData.height; - - var winWidth = opt_curWin && opt_curWin.innerWidth; - var winHeight = opt_curWin && opt_curWin.innerHeight; - var similarWin = opt_curWin && this.withinTol(adWidth, winWidth) && this.withinTol(adHeight, winHeight); - - var similarSizeX = this.withinTol(adWidth, cWidth); - var similarSizeY = this.withinTol(adHeight, cHeight); - var adAspect = adWidth / adHeight; - - return similarWin || el.tagName === 'A' || (adAspect >= this.ASPECT_RATIO_FOR_LEADERBOARDS && similarSizeY) || (similarSizeX && similarSizeY); - }, - - withinTol: function(adlen, conlen) { - var pct = (conlen - adlen) / adlen; - - return pct <= this.CONTAINER_SIZE_TOL; - }, - - serializeElements: function(el) { - if ( !el ) { - return; - } - var i; - var ifrWin; - var adId = this.adData.adId; - var elIsAd = false; - - if ( adId && el[adId] && el[adId].isAd === true ) { - elIsAd = true; - } - - var json = exports.parser.elementToJSON(el, elIsAd); - var childJSON; - - if ( elIsAd ) { - json.adId = adId; - this.adData.element = {}; - - var keys = Object.keys(json); - for ( i = 0; i < keys.length; i++ ) { - var key = keys[i]; - if ( key !== 'children' && key !== 'contents' ) { - this.adData.element[key] = json[key]; - } - } - } - - var children = exports.utils.realArray(el.children).filter(function(el) { - var param = el.tagName === 'PARAM'; - var inlineScript = el.tagName === 'SCRIPT' && !(el.src && el.src.indexOf('http') >= 0); - var noScript = el.tagName === 'NOSCRIPT'; - return !(param || inlineScript || noScript); - }); - - for ( i = 0; i < children.length; i++ ) { - childJSON = this.serializeElements(children[i]); - if ( childJSON ) { - json.children.push(childJSON); - } - } - - if ( el.tagName === 'IFRAME' ) { - ifrWin = el.contentWindow; - - if ( adId && el[adId] && el[adId].needsWindow ) { - - json.contents = this.adData.serializedIframeContents; - el[adId].needsWindow = false; - delete this.adData.serializedIframeContents; - - } else if ( exports.utils.isFriendlyWindow(ifrWin) ) { - - childJSON = this.serializeElements(ifrWin.document.documentElement); - if ( childJSON ) { - json.contents = childJSON; - } - } - } - - if ( json.children.length > 0 || json.adId || json.tagName === 'IFRAME' || json.url ) { - return json; - } else { - return null; - } - }, - - captureHTML: function(containerEl) { - this.adData.context = this.serializeElements(containerEl); - }, - - nodeCount: function(el) { - return el.getElementsByTagName('*').length + 1; - }, - - highestContainer: function(curWin, referenceElement) { - var curContainer = referenceElement; - var docEl = curWin.document.documentElement; - var parentContainer; - - if ( curWin !== curWin.top && this.isValidContainer(docEl, curWin) ) { - return docEl; - } - - while ( true ) { - parentContainer = curContainer.parentElement; - if ( parentContainer && this.isValidContainer(parentContainer) ) { - curContainer = parentContainer; - } else { - return curContainer; - } - } - } - }; - - var tagfinder = { - - setPositions: function(adData, opt_el, opt_winPos) { - var el = opt_el || adData.context; - var winPos = opt_winPos || {left: 0, top: 0}; - var ifrPos; - - el.left += winPos.left; - el.top += winPos.top; - - if ( el.children ) { - el.children.forEach(function(child) { - this.setPositions(adData, child, winPos); - }, this); - } - - if ( el.contents ) { - ifrPos = {left: el.left, top: el.top}; - this.setPositions(adData, el.contents, ifrPos); - } - - if ( el.adId === adData.adId ) { - adData.element.left = el.left; - adData.element.top = el.top; - } - }, - - appendTags: function(adData, referenceElement) { - var mgr = new ContextManager(adData); - var curWin = exports.utils.elementWindow(referenceElement); - var highestContainer; - - while ( true ) { - highestContainer = mgr.highestContainer(curWin, referenceElement); - mgr.captureHTML(highestContainer); - if ( curWin === curWin.top ) { - break; - } else { - - curWin.mpAdFound = true; - - mgr.adData.serializedIframeContents = mgr.adData.context; - - if ( exports.utils.isFriendlyWindow(curWin.parent) ) { - referenceElement = curWin.frameElement; - referenceElement[mgr.adData.adId] = {needsWindow: true}; - curWin = curWin.parent; - } else { - break; - } - } - } - return { - referenceElement:referenceElement, - highestContainer: highestContainer - }; - } - }; - - exports.tagfinder = tagfinder; -})(exports); - -(function(exports) { - var _onAdFound; - var _logGen = new exports.utils.LogGenerator(); - var _pageTags; - var INIT_MS_BW_SEARCHES = 2000; - var PAGE_TAG_RE = new RegExp('gpt|oascentral'); - var POST_MSG_ID = '1519242200-10756-12873-1462-13403'; - var AD_SERVER_RE = new RegExp('^(google_ads_iframe|oas_frame|atwAdFrame)'); - - function getPageTags(doc) { - var scripts = doc.getElementsByTagName('script'); - var pageTags = []; - scripts = exports.utils.realArray(scripts); - scripts.forEach(function(script) { - if ( PAGE_TAG_RE.exec(script.src) ) { - pageTags.push({'tagName': 'SCRIPT', 'url': script.src}); - } - }); - return pageTags; - } - - function messageAllParentFrames(adData) { - - adData.postMessageId = POST_MSG_ID; - - adData = JSON.stringify(adData); - - var win = window; - while ( win !== win.top ) { - win = win.parent; - win.postMessage(adData, '*'); - } - } - - function appendTagsAndSendToParent(adData, referenceElement) { - var results = exports.tagfinder.appendTags(adData, referenceElement); - if ( exports.utils.SCRIPT_IN_HOSTILE_IFRAME ) { - messageAllParentFrames(adData); - - } else if ( exports.utils.SCRIPT_IN_WINDOW_TOP ) { - - exports.tagfinder.setPositions(adData); - - adData.matchedSize = exports.adfinder.getMatchedAdSize(adData.width, adData.height); - if ( !adData.matchedSize ) { - - if ( AD_SERVER_RE.exec(results.referenceElement.id) ) { - adData.matchedSize = [adData.width, adData.height]; - adData.oddSize = true; - } else { - - return; - } - } - delete adData.width; - delete adData.height; - adData.curPageUrl = exports.utils.getPageUrl(); - _pageTags = _pageTags || getPageTags(document); - var log = _logGen.log('ad', [adData], _pageTags); - - if ( _onAdFound ) { - - _onAdFound(log, results.referenceElement); - - } - } - } - - function extractAdsWrapper() { - if ( exports.utils.SCRIPT_IN_WINDOW_TOP || document.readyState === 'complete' ) { - extractAds(); - } - setTimeout( - function() { extractAdsWrapper(); }, INIT_MS_BW_SEARCHES - ); - } - - function extractAds() { - var ads = exports.adfinder.findAds(window); - ads.forEach(function(ad) { - - var startTime = new Date().getTime(); - var adId = startTime + '-' + Math.floor(Math.random() * 10e12); - - var adData = { - width: Math.round(ad.offsetWidth), - height: Math.round(ad.offsetHeight), - startTime: startTime, - adId: adId, - html5: ad.html5 || false - }; - - if ( ad.html5 ) { - adData.adSizeMeta = ad.adSizeMeta || null; - adData.winClickTag = ad.winClickTag || null; - } - - ad[adId] = { isAd: true }; - - appendTagsAndSendToParent(adData, ad); - }); - } - - function isChildWin(myWin, otherWin) { - var parentWin = otherWin.parent; - while ( parentWin !== otherWin ) { - if ( parentWin === myWin ) { - return true; - } - otherWin = parentWin; - parentWin = parentWin.parent; - } - return false; - } - - function iframeFromWindow(win, winToMatch) { - var i, ifr, ifrWin, - iframes = win.document.querySelectorAll('iframe'); - - for ( i = 0; i < iframes.length; i++ ) { - ifr = iframes[i]; - if ( ifr.contentWindow === winToMatch ) { - return ifr; - } - } - - for ( i = 0; i < iframes.length; i++ ) { - ifrWin = iframes[i].contentWindow; - if ( exports.utils.isFriendlyWindow(ifrWin) ) { - ifr = iframeFromWindow(ifrWin, winToMatch); - if ( ifr ) { - return ifr; - } - } - } - } - - function onPostMessage(event) { - var adData, - ifrWin = event.source, - - myWin = window.document.defaultView, - ifrTag; - - try { - - adData = JSON.parse(event.data); - } catch(e) { - - return; - } - - if ( adData.postMessageId === POST_MSG_ID ) { - - delete adData.postMessageId; - - event.stopImmediatePropagation(); - - if ( isChildWin(myWin, ifrWin) ) { - if ( exports.utils.isFriendlyWindow(ifrWin) ) { - ifrTag = ifrWin.frameElement; - } else { - ifrTag = iframeFromWindow(myWin, ifrWin); - } - - if ( ifrTag ) { - ifrTag[adData.adId] = {needsWindow: true}; - appendTagsAndSendToParent(adData, ifrTag); - } - } - } - } - - function onVideoMessage(msg, sender, callback) { - var log; - if ( msg.event === 'new-video-ad' ) { - msg.assets.forEach(function(asset) { - - }); - log = _logGen.log('video', msg.assets); - } else { - log = _logGen.log('invalid-video', msg.assets); - } - - msg.assets.forEach(function(a) {delete a.isVideo;}); - log.displayAdFound = msg.displayAdFound; - log.requests = msg.requests; - log.data = msg.event_data; - - log.doc.finalPageUrl = log.doc.url; - log.doc.url = exports.utils.normalizeUrl(msg.origUrl); - - _onAdFound(log); - } - - function addBackgroundListener(event, callback) { - if ( typeof browser !== 'undefined' ) { - browser.runtime.onMessage.addListener(function(msg) { - if ( msg.event === event ) { - callback(msg); - } - }); - } else if ( typeof chrome !== 'undefined' ) { - chrome.runtime.onMessage.addListener(function(msg) { - if ( msg.event === event ) { - callback(msg); - } - }); - } else if ( window.self.port ) { - window.self.port.on(event, callback); - } - } - - exports.coordinator = { - addPostMessageListener: function() { - if ( !exports.utils.SCRIPT_IN_FRIENDLY_IFRAME ) { - window.addEventListener('message', onPostMessage, false); - } - }, - - blockedRobotsMsgGen: function(sendFcn, origUrl) { - - if ( origUrl.indexOf('google.com/_/chrome/newtab') === -1 ) { - var onBlockedRobotsMessage = function() { - var log; - log = _logGen.log('invalid-robotstxt', []); - log.doc.finalPageUrl = log.doc.url; - log.doc.url = exports.utils.normalizeUrl(origUrl); - - sendFcn(log); - }; - return onBlockedRobotsMessage; - } else { - return function() {}; - } - }, - - init: function(onAdFound) { - - if ( exports.utils.SCRIPT_IN_FRIENDLY_IFRAME ) { - return false; - } - - _onAdFound = onAdFound; - if ( exports.utils.SCRIPT_IN_WINDOW_TOP ) { - var log = _logGen.log('page'); - onAdFound(log); - - window.addEventListener('beforeunload', function(event) { - var log = _logGen.log('unload'); - log.timing = window.performance.timing; - onAdFound(log); - }); - - addBackgroundListener('new-video-ad', onVideoMessage); - addBackgroundListener('new-invalid-video-ad', onVideoMessage); - - } - - exports.utils.onDocLoaded(document, extractAdsWrapper); - } - }; - -})(exports); - -if ( exports.utils.SCRIPT_IN_WINDOW_TOP ) { - window.adparser = { - init: exports.coordinator.init, - addPostMessageListener: exports.coordinator.addPostMessageListener, - askIfTrackingEnabled: exports.utils.askIfTrackingEnabled, - blockedRobotsMsgGen: exports.coordinator.blockedRobotsMsgGen, - inWindowTop: exports.utils.SCRIPT_IN_WINDOW_TOP, - sendToBackground: exports.utils.sendToBackground - }; -} else { - exports.coordinator.addPostMessageListener(); - exports.utils.askIfTrackingEnabled( - function() { - exports.coordinator.init(function() {}); - }, - function() {} - ); -} -})(window); -(function(adparser, pageUrl) { - function onAdFound(log) { - adparser.sendToBackground({ id: 'ad_log', subject: log }, 'ad_log', '', function(){}); - } - - if ( adparser && adparser.inWindowTop ) { - adparser.addPostMessageListener(); - adparser.askIfTrackingEnabled( - function() { - adparser.init(onAdFound); - }, - adparser.blockedRobotsMsgGen(onAdFound, pageUrl) - ) - } -})(window.adparser, window.location.href); diff --git a/src/drivers/webextension/js/inject.js b/src/drivers/webextension/js/inject.js index 322c21374..a2458e1bd 100644 --- a/src/drivers/webextension/js/inject.js +++ b/src/drivers/webextension/js/inject.js @@ -1,12 +1,12 @@ (() => { - try { - const detectJs = chain => { + try { + const detectJs = (chain) => { const properties = chain.split('.'); let value = properties.length ? window : null; - for (let i = 0; i < properties.length; i ++) { - let property = properties[i]; + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; if (value && value.hasOwnProperty(property)) { value = value[property]; @@ -20,7 +20,7 @@ return typeof value === 'string' || typeof value === 'number' ? value : !!value; }; - const onMessage = event => { + const onMessage = (event) => { if (event.data.id !== 'patterns') { return; } @@ -31,15 +31,15 @@ const js = {}; - for (let appName in patterns) { + for (const appName in patterns) { if (patterns.hasOwnProperty(appName)) { js[appName] = {}; - for (let chain in patterns[appName]) { + for (const chain in patterns[appName]) { if (patterns[appName].hasOwnProperty(chain)) { js[appName][chain] = {}; - for (let index in patterns[appName][chain]) { + for (const index in patterns[appName][chain]) { const value = detectJs(chain); if (value && patterns[appName][chain].hasOwnProperty(index)) { @@ -55,7 +55,7 @@ }; addEventListener('message', onMessage); - } catch(e) { + } catch (e) { // Fail quietly } })(); diff --git a/src/drivers/webextension/js/jsontodom.js b/src/drivers/webextension/js/jsontodom.js deleted file mode 100644 index c6506c4fb..000000000 --- a/src/drivers/webextension/js/jsontodom.js +++ /dev/null @@ -1,63 +0,0 @@ -jsonToDOM.namespaces = { - html: "http://www.w3.org/1999/xhtml", - xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" -}; - -jsonToDOM.defaultNamespace = jsonToDOM.namespaces.html; - -function jsonToDOM(jsonTemplate, doc, nodes) { - function namespace(name) { - var reElemNameParts = /^(?:(.*):)?(.*)$/.exec(name); - return { namespace: jsonToDOM.namespaces[reElemNameParts[1]], shortName: reElemNameParts[2] }; - } - - // Note that 'elemNameOrArray' is: either the full element name (eg. [html:]div) or an array of elements in JSON notation - function tag(elemNameOrArray, elemAttr) { - // Array of elements? Parse each one... - if (Array.isArray(elemNameOrArray)) { - var frag = doc.createDocumentFragment(); - Array.prototype.forEach.call(arguments, function(thisElem) { - frag.appendChild(tag.apply(null, thisElem)); - }); - return frag; - } - - // Single element? Parse element namespace prefix (if none exists, default to defaultNamespace), and create element - var elemNs = namespace(elemNameOrArray); - var elem = doc.createElementNS(elemNs.namespace || jsonToDOM.defaultNamespace, elemNs.shortName); - - // Set element's attributes and/or callback functions (eg. onclick) - for (var key in elemAttr) { - var val = elemAttr[key]; - if (nodes && key == "key") { - nodes[val] = elem; - continue; - } - - var attrNs = namespace(key); - if (typeof val == "function") { - // Special case for function attributes; don't just add them as 'on...' attributes, but as events, using addEventListener - elem.addEventListener(key.replace(/^on/, ""), val, false); - } - else { - // Note that the default namespace for XML attributes is, and should be, blank (ie. they're not in any namespace) - elem.setAttributeNS(attrNs.namespace || "", attrNs.shortName, val); - } - } - - // Create and append this element's children - var childElems = Array.prototype.slice.call(arguments, 2); - childElems.forEach(function(childElem) { - if (childElem != null) { - elem.appendChild( - childElem instanceof doc.defaultView.Node ? childElem : - Array.isArray(childElem) ? tag.apply(null, childElem) : - doc.createTextNode(childElem)); - } - }); - - return elem; - } - - return tag.apply(null, jsonTemplate); -} diff --git a/src/drivers/webextension/js/lib/iframe.js b/src/drivers/webextension/js/lib/iframe.js new file mode 100644 index 000000000..2185a709e --- /dev/null +++ b/src/drivers/webextension/js/lib/iframe.js @@ -0,0 +1,1140 @@ + + +(function (win) { + const exports = {}; + + (function (exports) { + const utils = { + normalizeUrl(url) { + return this.hashUrl(url) || null; + }, + + getReferrer() { + return this.normalizeUrl(document.referrer); + }, + + getPageUrl() { + return this.normalizeUrl(window.location.href); + }, + hashUrl(url) { + let a, + result; + + if (!url || url.indexOf('http') !== 0) { + return null; + } + + a = document.createElement('a'); + a.href = url; + + result = `${a.protocol}//${a.hostname}/`; + + if (a.pathname && a.pathname !== '/') { + result += this.hashCode(a.pathname); + } + + if (a.search) { + result += `?${this.hashCode(a.search)}`; + } + + if (a.hash) { + result += `#${this.hashCode(a.hash)}`; + } + + return result; + }, + + hashCode(str) { + let hash = 0, + kar, + i; + + if (str.length === 0) { + return hash; + } + + for (i = 0; i < str.length; i++) { + kar = str.charCodeAt(i); + hash = ((hash << 5) - hash) + kar; + hash &= hash; + } + + return hash + Math.pow(2, 32); + }, + + realArray(a) { + return Array.prototype.slice.apply(a); + }, + + onDocLoaded(doc, callback) { + if (doc.readyState === 'loading') { + doc.addEventListener('DOMContentLoaded', callback); + } else { + callback(); + } + }, + + SCRIPT_IN_WINDOW_TOP: window === window.top, + + isFriendlyWindow(win) { + let href; + try { + href = win.location.href; + } catch (e) { + return false; + } + return true; + }, + + elementWindow(el) { + return el.ownerDocument.defaultView; + }, + + viewport(win) { + return { width: win.innerWidth, height: win.innerHeight }; + }, + + parseQS(qs) { + if (qs.indexOf('http') === 0) { + qs = qs.split('?')[1]; + } + let i, + kvs, + key, + val; + const dict = {}; + qs = qs.split('&'); + for (i = 0; i < qs.length; i++) { + kvs = qs[i].split('='); + key = kvs[0]; + val = kvs.slice(1).join('='); + try { + dict[key] = window.decodeURIComponent(val); + } catch (e) { + continue; + } + } + return dict; + }, + sendToBackground(message, event, responseMessage, onResponse) { + if (typeof browser !== 'undefined') { + const response = browser.runtime.sendMessage(message); + response.then(onResponse); + } else if (typeof chrome !== 'undefined') { + chrome.runtime.sendMessage(message, onResponse); + } else if (window.self.port) { + window.self.port.on(responseMessage, onResponse); + window.self.port.emit(event, message); + } + }, + + askIfTrackingEnabled(callback, elseCallback) { + this.sendToBackground( + 'is_tracking_enabled', + '', + 'tracking_enabled_response', + (message) => { + if (message && message.tracking_enabled) { + callback(); + } else { + elseCallback(); + } + }, + ); + }, + }; + + utils.SCRIPT_IN_FRIENDLY_IFRAME = !utils.SCRIPT_IN_WINDOW_TOP && utils.isFriendlyWindow(window.parent); + utils.SCRIPT_IN_HOSTILE_IFRAME = !utils.SCRIPT_IN_WINDOW_TOP && !utils.SCRIPT_IN_FRIENDLY_IFRAME; + + function LogGenerator() { + this.msgNum = 0; + this.pageMeta = { + url: utils.getPageUrl(), + isHP: window.location.pathname === '/', + referrer: utils.getReferrer(), + rand: Math.floor(Math.random() * 10e12), + startTime: new Date().getTime(), + }; + } + + LogGenerator.prototype = { + log(event, opt_assets, opt_pageTags) { + let opt_video_assets; + if (event === 'video' || event === 'invalid-video') { + opt_video_assets = opt_assets || []; + opt_assets = []; + } else { + opt_video_assets = []; + opt_assets = opt_assets || []; + } + const result = { + doc: this.pageMeta, + event, + video_assets: opt_video_assets, + assets: opt_assets, + version: '3', + mrev: '4aeaa5a-c', + msgNum: this.msgNum, + timestamp: new Date().getTime(), + pageVis: document.visibilityState, + pageFoc: document.hasFocus(), + pageTags: opt_pageTags || [], + }; + this.msgNum++; + return result; + }, + }; + + utils.LogGenerator = LogGenerator; + + exports.utils = utils; + }(exports)); + + (function (exports) { + const SizeMatcher = { + VALID_AD_SIZES: [ + [300, 50], + [320, 50], + [160, 600], + [300, 250], + [300, 600], + [300, 1050], + [336, 280], + [336, 850], + [468, 60], + [728, 90], + [728, 250], + [728, 270], + [970, 66], + [970, 90], + [970, 125], + [970, 250], + [970, 400], + [970, 415], + [1280, 100], + ], + + PX_SIZE_TOL: 10, + + getMatchedAdSize(width, height) { + if (!this.set) { + this.set = this._makeSizeSet(); + } + + return this.set[`${Math.round(width)}x${Math.round(height)}`]; + }, + + elementIsAdShaped(el) { + return !!this.getMatchedAdSizeForElement(el); + }, + + getMatchedAdSizeForElement(el) { + const rect = el.getBoundingClientRect(); + return this.getMatchedAdSize(rect.width, rect.height); + }, + + _makeSizeSet() { + const set = {}; + let i; + let xfuz; + let yfuz; + let size; + let width; + let height; + + for (i = 0; i < this.VALID_AD_SIZES.length; i++) { + for (xfuz = -this.PX_SIZE_TOL; xfuz <= this.PX_SIZE_TOL; xfuz++) { + for (yfuz = -this.PX_SIZE_TOL; yfuz <= this.PX_SIZE_TOL; yfuz++) { + size = this.VALID_AD_SIZES[i]; + width = size[0] + xfuz; + height = size[1] + yfuz; + set[`${width}x${height}`] = size; + } + } + } + return set; + }, + }; + + const Throttler = { + MAX_SEARCHES_PER_WINDOW: 10, + MAX_SEARCHES_PER_ELEMENT: 2, + + countSearch(el) { + if (typeof el.searches !== 'number') { + el.searches = 0; + } + + el.searches += 1; + }, + + throttle(el, max) { + if (typeof el.searches === 'number' && el.searches >= max) { + return true; + } + return false; + }, + + throttleElement(el) { + return this.throttle(el, this.MAX_SEARCHES_PER_ELEMENT); + }, + + throttleWin(win) { + return this.throttle(win, this.MAX_SEARCHES_PER_WINDOW); + }, + + getCount(el) { + return el.searches || 0; + }, + }; + + function TopSearcher(win) { + this.win = win; + this.doc = win.document; + } + + TopSearcher.prototype.search = function () { + let candidates = exports.utils.realArray(this.doc.querySelectorAll('img, object, embed')), + html5Ad, + ads = []; + + ads = ads.concat(candidates.filter((el) => { + if (!el.mpAdFound && !Throttler.throttleElement(el)) { + Throttler.countSearch(el); + if ((el.tagName !== 'IMG' || isStandardImage(el)) && SizeMatcher.elementIsAdShaped(el)) { + el.mpAdFound = true; + return true; + } + } + return false; + })); + + html5Ad = this._mainGetHTMLAd(); + if (html5Ad) { + html5Ad.html5 = true; + html5Ad.mpAdFound = true; + ads.push(html5Ad); + } + + return ads; + }; + + TopSearcher.prototype._mainGetHTMLAd = function () { + let styles = this.doc.querySelectorAll('div > style, div > link[rel="stylesheet"]'), + i, + div; + for (i = 0; i < styles.length; i++) { + div = styles[i].parentNode; + if (!div.mpAdFound && SizeMatcher.elementIsAdShaped(div) && this._jumpedOut(div)) { + return div; + } + } + }; + + TopSearcher.prototype._jumpedOut = function (el) { + let siblings, + ifrs; + siblings = exports.utils.realArray(el.parentNode.children); + ifrs = siblings.filter(el => el.tagName === 'IFRAME' && el.offsetWidth === 0 && el.offsetHeight === 0); + return ifrs.length > 0; + }; + + function IframeSearcher(win) { + this.MIN_AD_AREA = 14000; + this.MIN_WINDOW_PX = 10; + + this.win = win; + this.doc = win.document; + this.body = win.document.body; + this.winClickTag = win.clickTag; + this.adSizeMeta = this._getAdSizeMeta(); + this.numElementsInBody = (this.body && this.body.querySelectorAll('*').length) || 0; + + this.shouldSearchWindow = false; + if (!this.win.mpAdFound && this.body && !Throttler.throttleWin(this.win)) { + this.winWidth = this.win.innerWidth; + this.winHeight = this.win.innerHeight; + if (this._meetsMinAdSize(this.winWidth, this.winHeight) && !this._containsLargeIframes()) { + this.shouldSearchWindow = true; + } + } + } + + IframeSearcher.prototype.search = function () { + let ad; + + if (this.shouldSearchWindow) { + ad = this._search(); + if (ad) { + ad.mpAdFound = true; + win.mpAdFound = true; + return ad; + } + Throttler.countSearch(this.win); + } + + return null; + }; + + IframeSearcher.prototype._search = function () { + let _this = this, + stdCandidates, + html5Candidates, + stdEl, + html5El; + + stdCandidates = this.body.querySelectorAll('img, object, embed'); + + stdEl = getFirst(stdCandidates, (el) => { + if (!el.mpAdFound + && !Throttler.throttleElement(el) + && (el.tagName !== 'IMG' || isStandardImage(el)) + && _this._elementIsAtLeastAsBigAsWindow(el)) { + return true; + } + Throttler.countSearch(el); + return false; + }); + + if (stdEl) { + return stdEl; + } + + if (this._isHTML5Iframe()) { + html5Candidates = this.doc.querySelectorAll('body, canvas, button, video, svg, div'); + html5El = getFirst(html5Candidates, (el) => { + if (_this._elementIsAtLeastAsBigAsWindow(el)) { + return true; + } + Throttler.countSearch(el); + return false; + }); + } + + if (html5El) { + html5El.html5 = true; + html5El.winClickTag = this.winClickTag; + html5El.adSizeMeta = this.adSizeMeta; + return html5El; + } + + return null; + }; + + IframeSearcher.prototype._isHTML5Iframe = function () { + if (this.winClickTag || this.adSizeMeta) { + return true; + } + + if (this.doc.querySelectorAll('canvas', 'button', 'video', 'svg').length > 0) { + return true; + } + + if (this.numElementsInBody >= 5 && Throttler.getCount(this.win) > 0 && this.doc.querySelectorAll('div').length > 0) { + return true; + } + + return false; + }; + + IframeSearcher.prototype._elementIsAtLeastAsBigAsWindow = function (el) { + let rect = el.getBoundingClientRect(), + tol = 0.95; + + return rect.width >= (tol * this.winWidth) && rect.height >= (tol * this.winHeight); + }; + + IframeSearcher.prototype._meetsMinAdSize = function (width, height) { + return (width * height) >= this.MIN_AD_AREA; + }; + + IframeSearcher.prototype._containsLargeIframes = function () { + const iframes = this.doc.querySelectorAll('iframe'); + let rect; + let i; + for (i = 0; i < iframes.length; i++) { + rect = iframes[i].getBoundingClientRect(); + if (rect.width > this.MIN_WINDOW_PX || rect.height > this.MIN_WINDOW_PX) { + return true; + } + } + return false; + }; + + IframeSearcher.prototype._getAdSizeMeta = function () { + const adSizeMeta = this.doc.querySelectorAll('meta[name="ad.size"]'); + if (adSizeMeta.length > 0) { + return adSizeMeta[0].content; + } + return null; + }; + + function getFirst(arr, testFn) { + let i, + el; + for (i = 0; i < arr.length; i++) { + el = arr[i]; + if (testFn(el)) { + return el; + } + } + return null; + } + + function isStandardImage(img) { + return img.src && (img.parentNode.tagName === 'A' || img.getAttribute('onclick')); + } + + function getFriendlyIframes(win) { + let iframes = win.document.querySelectorAll('iframe'); + iframes = exports.utils.realArray(iframes); + const friendlyIframes = iframes.filter(ifr => exports.utils.isFriendlyWindow(ifr.contentWindow)); + return friendlyIframes; + } + + function findAds(win) { + let i, + iframes, + searcher, + ad, + ads = []; + + if (win === win.top) { + searcher = new TopSearcher(win); + ads = ads.concat(searcher.search()); + } else { + searcher = new IframeSearcher(win); + ad = searcher.search(); + if (ad) { + ads.push(ad); + } + } + + iframes = getFriendlyIframes(win); + for (i = 0; i < iframes.length; i++) { + ads = ads.concat(findAds(iframes[i].contentWindow)); + } + + return ads; + } + + exports.adfinder = { + getMatchedAdSize: SizeMatcher.getMatchedAdSize.bind(SizeMatcher), + findAds, + }; + }(exports)); + + (function (exports) { + const parser = { + TAGS_WITH_SRC_ATTR: { + IMG: true, + SCRIPT: true, + IFRAME: true, + EMBED: true, + }, + + MAX_ATTR_LEN: 100, + + getUrl(el, params) { + let url; + + if (this.TAGS_WITH_SRC_ATTR.hasOwnProperty(el.tagName)) { + url = el.src; + } else if (el.tagName === 'OBJECT') { + url = el.data || (params && params.movie) || null; + } else if (el.tagName === 'A') { + url = el.href; + } + + if (url && url.indexOf('http') === 0) { + return url; + } + return null; + }, + + getParams(el) { + if (el.tagName !== 'OBJECT') { + return null; + } + + let i, + child; + const params = {}; + const children = el.children; + for (i = 0; i < children.length; i++) { + child = children[i]; + if (child.tagName === 'PARAM' && child.name) { + params[child.name.toLowerCase()] = child.value; + } + } + return params; + }, + + getPosition(el) { + const rect = el.getBoundingClientRect(); + const win = exports.utils.elementWindow(el); + + return { + width: Math.round(rect.width), + height: Math.round(rect.height), + left: Math.round(rect.left + win.pageXOffset), + top: Math.round(rect.top + win.pageYOffset), + }; + }, + + getFlashvars(el, params, url) { + let flashvars; + const urlQS = url && url.split('?')[1]; + + if (el.tagName === 'EMBED') { + flashvars = el.getAttribute('flashvars') || urlQS; + } else if (el.tagName === 'OBJECT') { + flashvars = params.flashvars || el.getAttribute('flashvars') || urlQS; + } + + return (flashvars && exports.utils.parseQS(flashvars)) || null; + }, + + findClickThru(el, flashvars) { + let key; + if (el.tagName === 'IMG' && el.parentElement.tagName === 'A') { + return el.parentElement.href; + } if (flashvars) { + for (key in flashvars) { + if (flashvars.hasOwnProperty(key)) { + if (key.toLowerCase().indexOf('clicktag') === 0) { + return flashvars[key]; + } + } + } + } + return null; + }, + + getAttr(el, name) { + const val = el.getAttribute(name); + + if (val && val.slice && val.toString) { + return val.slice(0, this.MAX_ATTR_LEN).toString(); + } + return null; + }, + + putPropIfExists(obj, name, val) { + if (val) { + obj[name] = val; + } + }, + + putAttrIfExists(obj, el, name) { + const val = this.getAttr(el, name); + this.putPropIfExists(obj, name, val); + }, + + elementToJSON(el, opt_findClickThru) { + const pos = this.getPosition(el); + const params = this.getParams(el); + const url = this.getUrl(el, params); + const flashvars = this.getFlashvars(el, params, url); + const clickThru = opt_findClickThru && this.findClickThru(el, flashvars); + const json = { + tagName: el.tagName, + width: pos.width, + height: pos.height, + left: pos.left, + top: pos.top, + children: [], + }; + + if (params) { + delete params.flashvars; + } + + this.putAttrIfExists(json, el, 'id'); + this.putAttrIfExists(json, el, 'class'); + this.putAttrIfExists(json, el, 'name'); + + this.putPropIfExists(json, 'flashvars', flashvars); + this.putPropIfExists(json, 'url', url); + this.putPropIfExists(json, 'params', params); + this.putPropIfExists(json, 'clickThru', clickThru); + + return json; + }, + }; + + exports.parser = { elementToJSON: parser.elementToJSON.bind(parser) }; + }(exports)); + + (function (exports) { + const ContextManager = function (adData) { + this.adData = adData; + }; + + ContextManager.prototype = { + CONTAINER_SIZE_TOL: 0.4, + ASPECT_RATIO_FOR_LEADERBOARDS: 2, + + isValidContainer(el, opt_curWin) { + const cWidth = el.clientWidth; + const cHeight = el.clientHeight; + + const adWidth = this.adData.width; + const adHeight = this.adData.height; + + const winWidth = opt_curWin && opt_curWin.innerWidth; + const winHeight = opt_curWin && opt_curWin.innerHeight; + const similarWin = opt_curWin && this.withinTol(adWidth, winWidth) && this.withinTol(adHeight, winHeight); + + const similarSizeX = this.withinTol(adWidth, cWidth); + const similarSizeY = this.withinTol(adHeight, cHeight); + const adAspect = adWidth / adHeight; + + return similarWin || el.tagName === 'A' || (adAspect >= this.ASPECT_RATIO_FOR_LEADERBOARDS && similarSizeY) || (similarSizeX && similarSizeY); + }, + + withinTol(adlen, conlen) { + const pct = (conlen - adlen) / adlen; + + return pct <= this.CONTAINER_SIZE_TOL; + }, + + serializeElements(el) { + if (!el) { + return; + } + let i; + let ifrWin; + const adId = this.adData.adId; + let elIsAd = false; + + if (adId && el[adId] && el[adId].isAd === true) { + elIsAd = true; + } + + const json = exports.parser.elementToJSON(el, elIsAd); + let childJSON; + + if (elIsAd) { + json.adId = adId; + this.adData.element = {}; + + const keys = Object.keys(json); + for (i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key !== 'children' && key !== 'contents') { + this.adData.element[key] = json[key]; + } + } + } + + const children = exports.utils.realArray(el.children).filter((el) => { + const param = el.tagName === 'PARAM'; + const inlineScript = el.tagName === 'SCRIPT' && !(el.src && el.src.indexOf('http') >= 0); + const noScript = el.tagName === 'NOSCRIPT'; + return !(param || inlineScript || noScript); + }); + + for (i = 0; i < children.length; i++) { + childJSON = this.serializeElements(children[i]); + if (childJSON) { + json.children.push(childJSON); + } + } + + if (el.tagName === 'IFRAME') { + ifrWin = el.contentWindow; + + if (adId && el[adId] && el[adId].needsWindow) { + json.contents = this.adData.serializedIframeContents; + el[adId].needsWindow = false; + delete this.adData.serializedIframeContents; + } else if (exports.utils.isFriendlyWindow(ifrWin)) { + childJSON = this.serializeElements(ifrWin.document.documentElement); + if (childJSON) { + json.contents = childJSON; + } + } + } + + if (json.children.length > 0 || json.adId || json.tagName === 'IFRAME' || json.url) { + return json; + } + return null; + }, + + captureHTML(containerEl) { + this.adData.context = this.serializeElements(containerEl); + }, + + nodeCount(el) { + return el.getElementsByTagName('*').length + 1; + }, + + highestContainer(curWin, referenceElement) { + let curContainer = referenceElement; + const docEl = curWin.document.documentElement; + let parentContainer; + + if (curWin !== curWin.top && this.isValidContainer(docEl, curWin)) { + return docEl; + } + + while (true) { + parentContainer = curContainer.parentElement; + if (parentContainer && this.isValidContainer(parentContainer)) { + curContainer = parentContainer; + } else { + return curContainer; + } + } + }, + }; + + const tagfinder = { + + setPositions(adData, opt_el, opt_winPos) { + const el = opt_el || adData.context; + const winPos = opt_winPos || { left: 0, top: 0 }; + let ifrPos; + + el.left += winPos.left; + el.top += winPos.top; + + if (el.children) { + el.children.forEach(function (child) { + this.setPositions(adData, child, winPos); + }, this); + } + + if (el.contents) { + ifrPos = { left: el.left, top: el.top }; + this.setPositions(adData, el.contents, ifrPos); + } + + if (el.adId === adData.adId) { + adData.element.left = el.left; + adData.element.top = el.top; + } + }, + + appendTags(adData, referenceElement) { + const mgr = new ContextManager(adData); + let curWin = exports.utils.elementWindow(referenceElement); + let highestContainer; + + while (true) { + highestContainer = mgr.highestContainer(curWin, referenceElement); + mgr.captureHTML(highestContainer); + if (curWin === curWin.top) { + break; + } else { + curWin.mpAdFound = true; + + mgr.adData.serializedIframeContents = mgr.adData.context; + + if (exports.utils.isFriendlyWindow(curWin.parent)) { + referenceElement = curWin.frameElement; + referenceElement[mgr.adData.adId] = { needsWindow: true }; + curWin = curWin.parent; + } else { + break; + } + } + } + return { + referenceElement, + highestContainer, + }; + }, + }; + + exports.tagfinder = tagfinder; + }(exports)); + + (function (exports) { + let _onAdFound; + const _logGen = new exports.utils.LogGenerator(); + let _pageTags; + const INIT_MS_BW_SEARCHES = 2000; + const PAGE_TAG_RE = new RegExp('gpt|oascentral'); + const POST_MSG_ID = '1519242200-10756-12873-1462-13403'; + const AD_SERVER_RE = new RegExp('^(google_ads_iframe|oas_frame|atwAdFrame)'); + + function getPageTags(doc) { + let scripts = doc.getElementsByTagName('script'); + const pageTags = []; + scripts = exports.utils.realArray(scripts); + scripts.forEach((script) => { + if (PAGE_TAG_RE.exec(script.src)) { + pageTags.push({ tagName: 'SCRIPT', url: script.src }); + } + }); + return pageTags; + } + + function messageAllParentFrames(adData) { + adData.postMessageId = POST_MSG_ID; + + adData = JSON.stringify(adData); + + let win = window; + while (win !== win.top) { + win = win.parent; + win.postMessage(adData, '*'); + } + } + + function appendTagsAndSendToParent(adData, referenceElement) { + const results = exports.tagfinder.appendTags(adData, referenceElement); + if (exports.utils.SCRIPT_IN_HOSTILE_IFRAME) { + messageAllParentFrames(adData); + } else if (exports.utils.SCRIPT_IN_WINDOW_TOP) { + exports.tagfinder.setPositions(adData); + + adData.matchedSize = exports.adfinder.getMatchedAdSize(adData.width, adData.height); + if (!adData.matchedSize) { + if (AD_SERVER_RE.exec(results.referenceElement.id)) { + adData.matchedSize = [adData.width, adData.height]; + adData.oddSize = true; + } else { + return; + } + } + delete adData.width; + delete adData.height; + adData.curPageUrl = exports.utils.getPageUrl(); + _pageTags = _pageTags || getPageTags(document); + const log = _logGen.log('ad', [adData], _pageTags); + + if (_onAdFound) { + _onAdFound(log, results.referenceElement); + } + } + } + + function extractAdsWrapper() { + if (exports.utils.SCRIPT_IN_WINDOW_TOP || document.readyState === 'complete') { + extractAds(); + } + setTimeout( + () => { extractAdsWrapper(); }, INIT_MS_BW_SEARCHES, + ); + } + + function extractAds() { + const ads = exports.adfinder.findAds(window); + ads.forEach((ad) => { + const startTime = new Date().getTime(); + const adId = `${startTime}-${Math.floor(Math.random() * 10e12)}`; + + const adData = { + width: Math.round(ad.offsetWidth), + height: Math.round(ad.offsetHeight), + startTime, + adId, + html5: ad.html5 || false, + }; + + if (ad.html5) { + adData.adSizeMeta = ad.adSizeMeta || null; + adData.winClickTag = ad.winClickTag || null; + } + + ad[adId] = { isAd: true }; + + appendTagsAndSendToParent(adData, ad); + }); + } + + function isChildWin(myWin, otherWin) { + let parentWin = otherWin.parent; + while (parentWin !== otherWin) { + if (parentWin === myWin) { + return true; + } + otherWin = parentWin; + parentWin = parentWin.parent; + } + return false; + } + + function iframeFromWindow(win, winToMatch) { + let i, + ifr, + ifrWin, + iframes = win.document.querySelectorAll('iframe'); + + for (i = 0; i < iframes.length; i++) { + ifr = iframes[i]; + if (ifr.contentWindow === winToMatch) { + return ifr; + } + } + + for (i = 0; i < iframes.length; i++) { + ifrWin = iframes[i].contentWindow; + if (exports.utils.isFriendlyWindow(ifrWin)) { + ifr = iframeFromWindow(ifrWin, winToMatch); + if (ifr) { + return ifr; + } + } + } + } + + function onPostMessage(event) { + let adData, + ifrWin = event.source, + + myWin = window.document.defaultView, + ifrTag; + + try { + adData = JSON.parse(event.data); + } catch (e) { + return; + } + + if (adData.postMessageId === POST_MSG_ID) { + delete adData.postMessageId; + + event.stopImmediatePropagation(); + + if (isChildWin(myWin, ifrWin)) { + if (exports.utils.isFriendlyWindow(ifrWin)) { + ifrTag = ifrWin.frameElement; + } else { + ifrTag = iframeFromWindow(myWin, ifrWin); + } + + if (ifrTag) { + ifrTag[adData.adId] = { needsWindow: true }; + appendTagsAndSendToParent(adData, ifrTag); + } + } + } + } + + function onVideoMessage(msg, sender, callback) { + let log; + if (msg.event === 'new-video-ad') { + msg.assets.forEach((asset) => { + + }); + log = _logGen.log('video', msg.assets); + } else { + log = _logGen.log('invalid-video', msg.assets); + } + + msg.assets.forEach((a) => { delete a.isVideo; }); + log.displayAdFound = msg.displayAdFound; + log.requests = msg.requests; + log.data = msg.event_data; + + log.doc.finalPageUrl = log.doc.url; + log.doc.url = exports.utils.normalizeUrl(msg.origUrl); + + _onAdFound(log); + } + + function addBackgroundListener(event, callback) { + if (typeof browser !== 'undefined') { + browser.runtime.onMessage.addListener((msg) => { + if (msg.event === event) { + callback(msg); + } + }); + } else if (typeof chrome !== 'undefined') { + chrome.runtime.onMessage.addListener((msg) => { + if (msg.event === event) { + callback(msg); + } + }); + } else if (window.self.port) { + window.self.port.on(event, callback); + } + } + + exports.coordinator = { + addPostMessageListener() { + if (!exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { + window.addEventListener('message', onPostMessage, false); + } + }, + + blockedRobotsMsgGen(sendFcn, origUrl) { + if (origUrl.indexOf('google.com/_/chrome/newtab') === -1) { + const onBlockedRobotsMessage = function () { + let log; + log = _logGen.log('invalid-robotstxt', []); + log.doc.finalPageUrl = log.doc.url; + log.doc.url = exports.utils.normalizeUrl(origUrl); + + sendFcn(log); + }; + return onBlockedRobotsMessage; + } + return function () {}; + }, + + init(onAdFound) { + if (exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { + return false; + } + + _onAdFound = onAdFound; + if (exports.utils.SCRIPT_IN_WINDOW_TOP) { + const log = _logGen.log('page'); + onAdFound(log); + + window.addEventListener('beforeunload', (event) => { + const log = _logGen.log('unload'); + log.timing = window.performance.timing; + onAdFound(log); + }); + + addBackgroundListener('new-video-ad', onVideoMessage); + addBackgroundListener('new-invalid-video-ad', onVideoMessage); + } + + exports.utils.onDocLoaded(document, extractAdsWrapper); + }, + }; + }(exports)); + + if (exports.utils.SCRIPT_IN_WINDOW_TOP) { + window.adparser = { + init: exports.coordinator.init, + addPostMessageListener: exports.coordinator.addPostMessageListener, + askIfTrackingEnabled: exports.utils.askIfTrackingEnabled, + blockedRobotsMsgGen: exports.coordinator.blockedRobotsMsgGen, + inWindowTop: exports.utils.SCRIPT_IN_WINDOW_TOP, + sendToBackground: exports.utils.sendToBackground, + }; + } else { + exports.coordinator.addPostMessageListener(); + exports.utils.askIfTrackingEnabled( + () => { + exports.coordinator.init(() => {}); + }, + () => {}, + ); + } +}(window)); +(function (adparser, pageUrl) { + function onAdFound(log) { + adparser.sendToBackground({ id: 'ad_log', subject: log }, 'ad_log', '', () => {}); + } + + if (adparser && adparser.inWindowTop) { + adparser.addPostMessageListener(); + adparser.askIfTrackingEnabled( + () => { + adparser.init(onAdFound); + }, + adparser.blockedRobotsMsgGen(onAdFound, pageUrl), + ); + } +}(window.adparser, window.location.href)); diff --git a/src/drivers/webextension/js/lib/jsontodom.js b/src/drivers/webextension/js/lib/jsontodom.js new file mode 100644 index 000000000..24d9e4c29 --- /dev/null +++ b/src/drivers/webextension/js/lib/jsontodom.js @@ -0,0 +1,63 @@ +jsonToDOM.namespaces = { + html: 'http://www.w3.org/1999/xhtml', + xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', +}; + +jsonToDOM.defaultNamespace = jsonToDOM.namespaces.html; + +function jsonToDOM(jsonTemplate, doc, nodes) { + function namespace(name) { + const reElemNameParts = /^(?:(.*):)?(.*)$/.exec(name); + return { namespace: jsonToDOM.namespaces[reElemNameParts[1]], shortName: reElemNameParts[2] }; + } + + // Note that 'elemNameOrArray' is: either the full element name (eg. [html:]div) or an array of elements in JSON notation + function tag(elemNameOrArray, elemAttr) { + // Array of elements? Parse each one... + if (Array.isArray(elemNameOrArray)) { + const frag = doc.createDocumentFragment(); + Array.prototype.forEach.call(arguments, (thisElem) => { + frag.appendChild(tag(...thisElem)); + }); + return frag; + } + + // Single element? Parse element namespace prefix (if none exists, default to defaultNamespace), and create element + const elemNs = namespace(elemNameOrArray); + const elem = doc.createElementNS(elemNs.namespace || jsonToDOM.defaultNamespace, elemNs.shortName); + + // Set element's attributes and/or callback functions (eg. onclick) + for (const key in elemAttr) { + const val = elemAttr[key]; + if (nodes && key == 'key') { + nodes[val] = elem; + continue; + } + + const attrNs = namespace(key); + if (typeof val === 'function') { + // Special case for function attributes; don't just add them as 'on...' attributes, but as events, using addEventListener + elem.addEventListener(key.replace(/^on/, ''), val, false); + } else { + // Note that the default namespace for XML attributes is, and should be, blank (ie. they're not in any namespace) + elem.setAttributeNS(attrNs.namespace || '', attrNs.shortName, val); + } + } + + // Create and append this element's children + const childElems = Array.prototype.slice.call(arguments, 2); + childElems.forEach((childElem) => { + if (childElem != null) { + elem.appendChild( + childElem instanceof doc.defaultView.Node ? childElem + : Array.isArray(childElem) ? tag(...childElem) + : doc.createTextNode(childElem), + ); + } + }); + + return elem; + } + + return tag(...jsonTemplate); +} diff --git a/src/drivers/webextension/js/lib/network.js b/src/drivers/webextension/js/lib/network.js new file mode 100644 index 000000000..81cc45a54 --- /dev/null +++ b/src/drivers/webextension/js/lib/network.js @@ -0,0 +1,793 @@ + +(function () { + function isChrome() { + return (typeof chrome !== 'undefined' + && window.navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9\.]+)/)); + } + + let browserProxy; + if (isChrome()) { + browserProxy = chrome; + } else { + browserProxy = browser; + } + + const MIN_FF_MAJOR_VERSION = 51; + + const requiredBrowserApis = [ + browserProxy.webNavigation, + browserProxy.tabs, + browserProxy.webRequest, + browserProxy.runtime, + ]; + let areListenersRegistered = false; + const secBefore = 2000; + const secAfter = 5000; + const secBetweenDupAssets = 10e3; + const minVidSize = 500e3; + const maxVidSize = 25e6; + const maxContentRange = 25e6; + const videoExtensions = [ + 'af', '3gp', 'asf', 'avchd', 'avi', 'cam', 'dsh', 'flv', 'm1v', 'm2v', + 'fla', 'flr', 'sol', 'm4v', 'mkv', 'wrap', 'mng', 'mov', 'mpeg', 'mpg', + 'mpe', 'mp4', 'mxf', 'nsv', 'ogg', 'rm', 'svi', 'smi', 'wmv', 'webm', + ]; + const extensionsReg = new RegExp(`\\.${videoExtensions.join('$|\\.')}$`); + const videoContentTypesPrefixes = ['binary/octet-stream', 'video/', 'flv-application/', 'media']; + + const bannedContentTypes = ['video/mp2t', 'video/f4m', 'video/f4f']; + const bannedFiletypes = ['ts']; + const bannedFiletypesReg = new RegExp(`\\.${bannedFiletypes.join('$|\\.')}$`); + const whitelistReqTypes = ['object', 'xmlhttprequest', 'other']; + + const topVideoAssetDomains = [ + '2mdn.net', + 'adap.tv', + 'adnxs.com', + 'adsrvr.org', + 'btrll.com', + 'celtra.com', + 'flashtalking.com', + 'flite.com', + 'innovid.com', + 'jivox.com', + 'mixpo.com', + 'nytimes.com', + 'playwire.com', + 'selectmedia.asia', + 'serving-sys.com', + 'solvemedia.com', + 'spotible.com', + 'teads.tv', + 'tribalfusion.com', + 'tubemogul.com', + 'videologygroup.com', + 'washingtonpost.com', + ]; + + const robotsTxtAllows = wappalyzer.robotsTxtAllows.bind(wappalyzer); + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + const subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) + || Math.floor(position) !== position || position > subjectString.length) { + position = subjectString.length; + } + position -= searchString.length; + const lastIndex = subjectString.indexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + } + + function getFrame(getFrameDetails, callback) { + if (typeof chrome !== 'undefined') { + chrome.webNavigation.getFrame(getFrameDetails, callback); + } else if (typeof browser !== 'undefined') { + const gettingFrame = browser.webNavigation.getFrame(getFrameDetails); + gettingFrame.then(callback); + } + } + + function ifBrowserValid(callback, elseCallback) { + if (isChrome()) { + callback(); + } else if (typeof browser !== 'undefined') { + try { + const gettingInfo = browser.runtime.getBrowserInfo(); + gettingInfo.then((browserInfo) => { + const browserVersion = parseInt(browserInfo.version.split('.')[0]); + + if (browserInfo.name === 'Firefox' + && browserVersion >= MIN_FF_MAJOR_VERSION) { + callback(); + } else { + elseCallback(); + } + }); + } catch (err) { + elseCallback(); + } + } else { + elseCallback(); + } + } + + function ifTrackingEnabled(details, ifCallback, elseCallback) { + const fullIfCallback = function () { + allowedByRobotsTxt(details, ifCallback, elseCallback); + }; + + browser.storage.local.get('tracking').then((item) => { + if (item.hasOwnProperty('tracking')) { + if (item.tracking) { + fullIfCallback(); + } else { + elseCallback(); + } + } else { + fullIfCallback(); + } + }); + } + + function allowedByRobotsTxt(details, ifCallback, elseCallback) { + if (details.url && !details.url.startsWith('chrome://')) { + robotsTxtAllows(details.url).then(ifCallback, elseCallback); + } else { + elseCallback(); + } + } + + function isPixelRequest(request) { + return (request.type === 'image' || request.responseStatus === 204) + && request.size <= 1000; + } + + function isVpaidOrVastRequest(request) { + const lowerCaseUrl = request.url.toLowerCase(); + return lowerCaseUrl.indexOf('vpaid') !== -1 || lowerCaseUrl.indexOf('vast') !== -1; + } + + function hasValidRequestType(request) { + return whitelistReqTypes.indexOf(request.type) >= 0; + } + + function stripQueryParams(url) { + return url.split('?', 1)[0]; + } + + function parseHostnameFromUrl(url) { + const parser = document.createElement('a'); + parser.href = url; + return parser.hostname; + } + + function hasDomain(url, domain) { + return parseHostnameFromUrl(url).endsWith(domain); + } + + function findHeader(headers, key) { + let header; + for (let i = 0; i < headers.length; i += 1) { + header = headers[i]; + if (header.name.toLowerCase() === key) { + return header; + } + } + return null; + } + + function validVideoType(vtype) { + const goodType = videoContentTypesPrefixes.some(prefix => vtype.indexOf(prefix) === 0); + return goodType; + } + + function assetMsgKey(assetReq) { + const url = stripQueryParams(assetReq.url); + const key = `${assetReq.frameId}-${url}`; + return key; + } + + const PageNetworkTrafficCollector = function (tabId) { + this.tabId = tabId; + this.displayAdFound = false; + this.requests = {}; + this.msgsBeingSent = {}; + this.assetsSeen = {}; + this.allRedirects = {}; + }; + + var globalPageContainer = { + collectors: {}, + dyingCollectors: {}, + + cleanupCollector(tabId) { + if (tabId in this.collectors) { + delete globalPageContainer.collectors[tabId]; + } + }, + + onNewNavigation(details) { + const tabId = details.tabId; + this.cleanupCollector(tabId); + + ifTrackingEnabled( + details, + () => { + if (!areListenersRegistered) { + registerListeners(); + } + this.collectors[tabId] = new PageNetworkTrafficCollector(tabId); + }, + () => { + if (areListenersRegistered) { + unregisterListeners(); + } + }, + ); + }, + + onNavigationCommitted(details) { + + }, + + onNavigationCompleted(details) { + + }, + + onTabClose(tabId, closeInfo) { + this.cleanupCollector(tabId); + delete this.collectors[tabId]; + }, + + onDisplayAdFound(tabId) { + this.collectors[tabId].displayAdFound = true; + }, + + getRandId() { + return String(Math.floor(Math.random() * 1e9)); + }, + + getCollector(tabId) { + if (this.collectors.hasOwnProperty(tabId)) { + return this.collectors[tabId]; + } + return null; + }, + + forwardCall(details, collectorMemberFunction) { + const collector = this.getCollector(details.tabId); + if (collector !== null) { + collectorMemberFunction.apply(collector, [details]); + } + }, + }; + + PageNetworkTrafficCollector.prototype.sendLogMessageToTabConsole = function () { + const logMessage = Array.from(arguments).join(' '); + const message = { message: logMessage, event: 'console-log-message' }; + browserProxy.tabs.sendMessage(this.tabId, message); + }; + + PageNetworkTrafficCollector.prototype.sendToTab = function (assetReq, reqs, curPageUrl, adTrackingEvent) { + const msg = {}; + msg.assets = []; + msg.requests = []; + msg.event_data = {}; + msg.event = adTrackingEvent; + if (adTrackingEvent === 'new-video-ad') { + msg.requests = reqs; + msg.requests.sort((reqA, reqB) => reqA.requestTimestamp - reqB.requestTimestamp); + if (assetReq) { + msg.assets = [assetReq]; + } + } else if (adTrackingEvent === 'new-invalid-video-ad') { + msg.requests = reqs.map(request => parseHostnameFromUrl(request.url)); + msg.assets = [{ + + url: parseHostnameFromUrl(assetReq.url), + + contentType: assetReq.contentType, + size: assetReq.size, + }]; + } + msg.origUrl = curPageUrl; + msg.displayAdFound = this.displayAdFound; + + browserProxy.tabs.sendMessage(this.tabId, msg); + }; + + PageNetworkTrafficCollector.prototype.getRedirKey = function (url, frameId) { + return `${url}:${frameId}`; + }; + + PageNetworkTrafficCollector.prototype.seenBefore = function (request) { + const oldTime = this.assetsSeen[assetMsgKey(request)]; + if (oldTime && (request.requestTimestamp - oldTime < secBetweenDupAssets)) { + return true; + } + return false; + }; + + PageNetworkTrafficCollector.prototype.recordSeenAsset = function (request) { + this.assetsSeen[assetMsgKey(request)] = request.requestTimestamp; + }; + + PageNetworkTrafficCollector.prototype.onBeforeRequest = function (details) { + const req = { + url: details.url, + type: details.type, + httpMethod: details.method, + frameId: details.frameId, + parentFrameId: details.parentFrameId, + requestTimestamp: details.timeStamp, + }; + this.requests[details.requestId] = req; + }; + + PageNetworkTrafficCollector.prototype.onSendHeaders = function (details) { + let request, + header; + request = this.requests[details.requestId]; + header = request && findHeader(details.requestHeaders, 'x-requested-with'); + if (header && header.value.toLowerCase().indexOf('flash') > -1) { + request.from_flash = true; + } + }; + + PageNetworkTrafficCollector.prototype.onHeadersReceived = function (details) { + const getFrameDetails = { + tabId: details.tabId, + processId: null, + frameId: details.frameId, + }; + const pageNetworkTrafficController = this; + getFrame(getFrameDetails, (frameDetails) => { + if (frameDetails && frameDetails.url) { + pageNetworkTrafficController._onHeadersReceived(details, frameDetails); + } + }); + }; + + PageNetworkTrafficCollector.prototype._onHeadersReceived = function (details, frameDetails) { + let contentSize, + contentRange; + + const request = this.requests[details.requestId]; + if (request) { + const redirParent = this.allRedirects[this.getRedirKey(details.url, details.frameId)]; + let header = request && findHeader(details.responseHeaders, 'content-type'); + const contentType = header && header.value.toLowerCase(); + + if (contentType) { + request.contentType = contentType; + } + header = request && findHeader(details.responseHeaders, 'content-length'); + contentSize = header && header.value; + if (contentSize) { + request.size = request.size || 0; + request.size += parseInt(contentSize); + } + header = request && findHeader(details.responseHeaders, 'content-range'); + contentRange = header && header.value; + if (contentRange) { + request.contentRange = parseInt(contentRange.split('/')[1]); + } + + let frameUrl = null; + if (frameDetails && frameDetails.url) { + frameUrl = frameDetails.url; + } + if (!this.bannedRequest(request) + && (this.isVideoReq(frameUrl, request) || (redirParent && redirParent.isVideo))) { + request.isVideo = true; + } + } + }; + + PageNetworkTrafficCollector.prototype.onBeforeRedirect = function (details) { + const request = this.requests[details.requestId]; + if (request) { + if (request.redirects) { + request.redirects.push(details.redirectUrl); + } else { + request.redirects = [details.redirectUrl]; + } + this.allRedirects[this.getRedirKey(details.redirectUrl, details.frameId)] = request; + } + }; + + PageNetworkTrafficCollector.prototype.isYoutubeMastheadRequest = function (url) { + const re = /video_masthead/; + return this.hasYoutubeDomain(url) && re.test(url); + }; + PageNetworkTrafficCollector.prototype.isYoutubeVideoRequest = function (srcUrl, destUrl) { + if (!this.hasYoutubeDomain(srcUrl)) { + return false; + } + + const re = /https?:\/\/r.*?\.googlevideo\.com\/videoplayback\?/; + return re.test(destUrl); + }; + PageNetworkTrafficCollector.prototype.processResponse = function (requestDetails, frameDetails) { + let request; + if (requestDetails) { + request = this.requests[requestDetails.requestId]; + if (request) { + request.responseStatus = requestDetails.statusCode; + request.responseTimestamp = requestDetails.timeStamp; + + let frameUrl = null; + if (frameDetails && frameDetails.url) { + frameUrl = frameDetails.url; + } + + let requestUrl = null; + if (request.url) { + requestUrl = request.url; + } + + if (this.isYoutubeAdReq(frameUrl, requestUrl)) { + const destVideoId = this.parseYoutubeVideoIdFromUrl(requestUrl); + const srcVideoId = this.parseYoutubeVideoIdFromUrl(frameUrl); + if (srcVideoId && destVideoId) { + request.isYoutubeAd = true; + request.isVideo = true; + request.rawSrcUrl = frameUrl; + request.rawDestUrl = requestUrl; + request.url = `https://www.youtube.com/watch?v=${this.parseYoutubeVideoIdFromUrl(requestUrl)}`; + } + } else if (!this.bannedRequest(request) + && (this.isVideo || this.isVideoReq(frameUrl, request))) { + request.isVideo = true; + } + + if (request.isVideo) { + const msgKey = assetMsgKey(request); + this.msgsBeingSent[msgKey] = request; + if (!this.seenBefore(request)) { + this.sendMsgWhenQuiet(msgKey); + } + this.recordSeenAsset(request); + } + } + } + }; + + PageNetworkTrafficCollector.prototype.onResponseStarted = function (responseDetails) { + if (responseDetails.frameId < 0) { + responseDetails.frameId = 99999; + } + const getFrameDetails = { + tabId: responseDetails.tabId, + processId: null, + frameId: responseDetails.frameId, + }; + const pageNetworkTrafficController = this; + getFrame(getFrameDetails, (frameDetails) => { + if (frameDetails && frameDetails.url) { + pageNetworkTrafficController.processResponse(responseDetails, frameDetails); + } + }); + }; + + PageNetworkTrafficCollector.prototype.hasBannedFiletype = function (request) { + const url = stripQueryParams(request.url); + if (bannedFiletypesReg.exec(url)) { + return true; + } + return false; + }; + + PageNetworkTrafficCollector.prototype.checkContentHeaders = function (request) { + if (request.contentType && validVideoType(request.contentType)) { + return true; + } + return false; + }; + + PageNetworkTrafficCollector.prototype.checkUrlExtension = function (request) { + const url = stripQueryParams(request.url); + if (extensionsReg.exec(url)) { + return true; + } + return false; + }; + + PageNetworkTrafficCollector.prototype.isVideoReq = function (srcUrl, request) { + if (this.isYoutubeVideoRequest(srcUrl, request.url)) { + return false; + } + return this.checkUrlExtension(request) || this.checkContentHeaders(request); + }; + PageNetworkTrafficCollector.prototype.hasYoutubeDomain = function (url) { + const hostname = parseHostnameFromUrl(url); + if (hostname === 'www.youtube.com') { + return true; + } + return false; + }; + PageNetworkTrafficCollector.prototype.parseYoutubeVideoIdFromUrl = function (url) { + let re = /^https?:\/\/www\.youtube\.com\/get_video_info.*(?:\?|&)video_id=(.*?)(?:$|&)/; + let match = re.exec(url); + if (match && match.length > 1) { + return match[1]; + } + + re = /^https?:\/\/www\.youtube\.com\/embed\/(.*?)(?:$|\?)/; + match = re.exec(url); + if (match && match.length > 1) { + return match[1]; + } + + re = /^https?:\/\/www\.youtube\.com\/watch.*(\?|&)v=([^&]*)/; + match = re.exec(url); + if (match && match.length > 1) { + return match[1]; + } + return null; + }; + + PageNetworkTrafficCollector.prototype.isYoutubeGetVideoInfoReq = function (url) { + const re = /^https?:\/\/www\.youtube\.com\/get_video_info\?/; + return re.test(url); + }; + PageNetworkTrafficCollector.prototype.isYoutubeAdReq = function (srcUrl, destUrl) { + if (!this.hasYoutubeDomain(srcUrl) + || !this.isYoutubeGetVideoInfoReq(destUrl)) { + return false; + } + if (this.parseYoutubeVideoIdFromUrl(srcUrl) + === this.parseYoutubeVideoIdFromUrl(destUrl) + && !this.isYoutubeMastheadRequest(destUrl)) { + return false; + } + return true; + }; + + PageNetworkTrafficCollector.prototype.bannedRequest = function (request) { + return this.bannedVideoType(request) || this.hasBannedFiletype(request) || this.bannedVideoSize(request); + }; + + PageNetworkTrafficCollector.prototype.bannedVideoType = function (request) { + let badType = false; + if (request.contentType) { + badType = bannedContentTypes.some(prefix => request.contentType.indexOf(prefix) >= 0); + } + return badType; + }; + + PageNetworkTrafficCollector.prototype.bannedVideoSize = function (request) { + if (request.size !== null) { + if (request.size < minVidSize || request.size > maxVidSize || request.contentRange > maxContentRange) { + return true; + } + } + return false; + }; + + PageNetworkTrafficCollector.prototype.grabTagReqs = function (tabRequests, assetRequest) { + let minTimestamp, + maxTimestamp; + minTimestamp = assetRequest.requestTimestamp - secBefore; + maxTimestamp = assetRequest.requestTimestamp + secAfter; + + const filteredRequests = tabRequests.filter(request => (request.requestTimestamp > minTimestamp + && request.requestTimestamp < maxTimestamp + && request.frameId === assetRequest.frameId + && request.url !== assetRequest.url + && (hasValidRequestType(request) + || isPixelRequest(request)))); + + return filteredRequests; + }; + + PageNetworkTrafficCollector.prototype.isValidVideoAd = function (assetRequest, tagRequests) { + const hasVpaidOrVastRequest = tagRequests.some(tagRequest => isVpaidOrVastRequest(tagRequest)); + + if (assetRequest.isYoutubeAd) { + return true; + } + if (hasVpaidOrVastRequest) { + return true; + } + const hasTopVideoAssetDomain = topVideoAssetDomains.some(assetDomain => hasDomain(assetRequest.url, assetDomain)); + + return hasTopVideoAssetDomain; + }; + + PageNetworkTrafficCollector.prototype.sendMsgWhenQuiet = function (msgKey) { + let _this = this, + origPageUrl, + msgAssetReq; + msgAssetReq = this.msgsBeingSent[msgKey]; + browserProxy.tabs.get(this.tabId, (tab) => { origPageUrl = tab.url; }); + + setTimeout(() => { + const rawRequests = []; + if (globalPageContainer.collectors[_this.tabId] === _this) { + for (const reqId in _this.requests) { + rawRequests.push(_this.requests[reqId]); + } + const tagReqs = _this.grabTagReqs(rawRequests, msgAssetReq); + + if (_this.isValidVideoAd(msgAssetReq, tagReqs)) { + _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-video-ad'); + } else { + _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-invalid-video-ad'); + } + } else { + + } + delete _this.msgsBeingSent[msgKey]; + }, secAfter + secBefore); + }; + + PageNetworkTrafficCollector.prototype.existingMessage = function (candidateRequest) { + const frameMsg = this.msgsBeingSent[candidateRequest.frameId]; + if (frameMsg) { + return frameMsg; + } + return null; + }; + + function onBeforeRequestListener(details) { + globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRequest); + } + + function onSendHeadersListener(details) { + globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onSendHeaders); + } + + function onHeadersReceivedListener(details) { + globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onHeadersReceived); + } + + function onBeforeRedirectListener(details) { + globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRedirect); + } + + function onResponseStartedListener(details) { + globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onResponseStarted); + } + + function onCommittedListener(details) { + if (details.frameId === 0) { + globalPageContainer.onNavigationCommitted(details); + } + } + + function onCompletedListener(details) { + if (details.frameId === 0) { + globalPageContainer.onNavigationCompleted(details); + } + } + + function onRemovedListener(tabId, closeInfo) { + globalPageContainer.onTabClose(tabId, closeInfo); + } + + function onMessageListener(message, sender, sendResponse) { + if (message.event === 'new-ad' && message.data.event === 'ad') { + const tabId = sender.tab.id; + if (tabId) { + globalPageContainer.onDisplayAdFound(tabId); + } + } + } + + function registerListeners() { + browserProxy.webRequest.onBeforeRequest.addListener( + onBeforeRequestListener, + { urls: ['http://*/*', 'https://*/*'] }, + [], + ); + + browserProxy.webRequest.onSendHeaders.addListener( + onSendHeadersListener, + { urls: ['http://*/*', 'https://*/*'] }, + ['requestHeaders'], + ); + + browserProxy.webRequest.onHeadersReceived.addListener( + onHeadersReceivedListener, + { urls: ['http://*/*', 'https://*/*'] }, + ['responseHeaders'], + ); + + browserProxy.webRequest.onBeforeRedirect.addListener( + onBeforeRedirectListener, + { urls: ['http://*/*', 'https://*/*'] }, + [], + ); + + browserProxy.webRequest.onResponseStarted.addListener( + onResponseStartedListener, + { urls: ['http://*/*', 'https://*/*'] }, + ['responseHeaders'], + ); + + browserProxy.webNavigation.onCommitted.addListener(onCommittedListener); + browserProxy.webNavigation.onCompleted.addListener(onCompletedListener); + browserProxy.tabs.onRemoved.addListener(onRemovedListener); + browserProxy.runtime.onMessage.addListener(onMessageListener); + + areListenersRegistered = true; + } + + function unregisterListeners() { + browserProxy.webRequest.onBeforeRequest.removeListener( + onBeforeRequestListener, + ); + + browserProxy.webRequest.onSendHeaders.removeListener( + onSendHeadersListener, + ); + + browserProxy.webRequest.onHeadersReceived.removeListener( + onHeadersReceivedListener, + ); + + browserProxy.webRequest.onBeforeRedirect.removeListener( + onBeforeRedirectListener, + ); + + browserProxy.webRequest.onResponseStarted.removeListener( + onResponseStartedListener, + ); + + browserProxy.webNavigation.onCommitted.removeListener(onCommittedListener); + browserProxy.webNavigation.onCompleted.removeListener(onCompletedListener); + browserProxy.tabs.onRemoved.removeListener(onRemovedListener); + browserProxy.runtime.onMessage.removeListener(onMessageListener); + + areListenersRegistered = false; + } + + function areRequiredBrowserApisAvailable() { + return requiredBrowserApis.every(api => typeof api !== 'undefined'); + } + + if (areRequiredBrowserApisAvailable()) { + ifBrowserValid( + () => { + browserProxy.webNavigation.onBeforeNavigate.addListener( + (details) => { + if (details.frameId === 0) { + globalPageContainer.onNewNavigation(details); + } + }, + { + url: [{ urlMatches: 'http://*/*' }, { urlMatches: 'https://*/*' }], + }, + ); + }, () => { + + }, + ); + } + + browserProxy.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request === 'is_browser_valid') { + ifBrowserValid( + sendResponse({ browser_valid: true }), + sendResponse({ browser_valid: false }), + ); + } + }); + + browserProxy.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request === 'is_tracking_enabled') { + ifTrackingEnabled( + sender.tab, + () => { + try { sendResponse({ tracking_enabled: true }); } catch (err) {} + }, + () => { + try { sendResponse({ tracking_enabled: false }); } catch (err) {} + }, + ); + } + return true; + }); +}()); diff --git a/src/drivers/webextension/js/network.js b/src/drivers/webextension/js/network.js deleted file mode 100644 index 6df8237ca..000000000 --- a/src/drivers/webextension/js/network.js +++ /dev/null @@ -1,824 +0,0 @@ -'use strict'; -(function() { - - function isChrome() { - return (typeof chrome !== 'undefined' && - window.navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9\.]+)/)); - } - - var browserProxy; - if ( isChrome() ) { - browserProxy = chrome; - } else { - browserProxy = browser; - } - - var MIN_FF_MAJOR_VERSION = 51; - - var requiredBrowserApis = [ - browserProxy.webNavigation, - browserProxy.tabs, - browserProxy.webRequest, - browserProxy.runtime - ]; - var areListenersRegistered = false; - var secBefore = 2000; - var secAfter = 5000; - var secBetweenDupAssets = 10e3; - var minVidSize = 500e3; - var maxVidSize = 25e6; - var maxContentRange = 25e6; - var videoExtensions = [ - 'af', '3gp', 'asf', 'avchd', 'avi', 'cam', 'dsh', 'flv', 'm1v', 'm2v', - 'fla', 'flr', 'sol', 'm4v', 'mkv', 'wrap', 'mng', 'mov', 'mpeg', 'mpg', - 'mpe', 'mp4', 'mxf', 'nsv', 'ogg', 'rm', 'svi', 'smi', 'wmv', 'webm' - ]; - var extensionsReg = new RegExp('\\.' + videoExtensions.join('$|\\.') + '$'); - var videoContentTypesPrefixes = ['binary/octet-stream', 'video/', 'flv-application/', 'media']; - - var bannedContentTypes = ['video/mp2t','video/f4m','video/f4f']; - var bannedFiletypes = ['ts']; - var bannedFiletypesReg = new RegExp('\\.' + bannedFiletypes.join('$|\\.') + '$'); - var whitelistReqTypes = ['object', 'xmlhttprequest', 'other']; - - var topVideoAssetDomains = [ - '2mdn.net', - 'adap.tv', - 'adnxs.com', - 'adsrvr.org', - 'btrll.com', - 'celtra.com', - 'flashtalking.com', - 'flite.com', - 'innovid.com', - 'jivox.com', - 'mixpo.com', - 'nytimes.com', - 'playwire.com', - 'selectmedia.asia', - 'serving-sys.com', - 'solvemedia.com', - 'spotible.com', - 'teads.tv', - 'tribalfusion.com', - 'tubemogul.com', - 'videologygroup.com', - 'washingtonpost.com' - ]; - - var robotsTxtAllows = wappalyzer.robotsTxtAllows.bind(wappalyzer); - if ( !String.prototype.endsWith ) { - String.prototype.endsWith = function(searchString, position) { - var subjectString = this.toString(); - if ( typeof position !== 'number' || !isFinite(position) || - Math.floor(position) !== position || position > subjectString.length) { - position = subjectString.length; - } - position -= searchString.length; - var lastIndex = subjectString.indexOf(searchString, position); - return lastIndex !== -1 && lastIndex === position; - }; - } - - function getFrame(getFrameDetails, callback) { - if ( typeof chrome !== 'undefined' ) { - chrome.webNavigation.getFrame(getFrameDetails, callback); - } else if ( typeof browser !== 'undefined' ) { - var gettingFrame = browser.webNavigation.getFrame(getFrameDetails); - gettingFrame.then(callback); - } - } - - function ifBrowserValid(callback, elseCallback) { - if ( isChrome() ) { - - callback(); - } else if ( typeof browser !== 'undefined' ) { - try { - var gettingInfo = browser.runtime.getBrowserInfo(); - gettingInfo.then(function(browserInfo) { - var browserVersion = parseInt(browserInfo.version.split('.')[0]); - - if ( browserInfo.name === 'Firefox' && - browserVersion >= MIN_FF_MAJOR_VERSION) { - callback(); - } else { - elseCallback(); - } - }); - } catch (err) { - - elseCallback(); - } - } else { - elseCallback(); - } - } - - function ifTrackingEnabled(details, ifCallback, elseCallback) { - - var fullIfCallback = function() { - allowedByRobotsTxt(details, ifCallback, elseCallback); - }; - - browser.storage.local.get('tracking').then(function(item) { - - if ( item.hasOwnProperty('tracking') ) { - if ( item.tracking ) { - fullIfCallback(); - } else { - elseCallback(); - } - } else { - fullIfCallback(); - } - }); - - } - - function allowedByRobotsTxt(details, ifCallback, elseCallback) { - if ( details.url && !details.url.startsWith('chrome://') ) { - robotsTxtAllows(details.url).then(ifCallback, elseCallback); - } else { - elseCallback(); - } - } - - function isPixelRequest(request) { - return (request.type === 'image' || request.responseStatus === 204) && - request.size <= 1000; - } - - function isVpaidOrVastRequest(request) { - var lowerCaseUrl = request.url.toLowerCase(); - return lowerCaseUrl.indexOf('vpaid') !== -1 || lowerCaseUrl.indexOf('vast') !== -1; - } - - function hasValidRequestType(request) { - return whitelistReqTypes.indexOf(request.type) >= 0; - } - - function stripQueryParams(url) { - return url.split('?', 1)[0]; - } - - function parseHostnameFromUrl(url) { - var parser = document.createElement('a'); - parser.href = url; - return parser.hostname; - } - - function hasDomain(url, domain) { - return parseHostnameFromUrl(url).endsWith(domain); - } - - function findHeader(headers, key) { - var header; - for ( var i = 0; i < headers.length; i += 1 ) { - header = headers[i]; - if ( header.name.toLowerCase() === key ) { - return header; - } - } - return null; - } - - function validVideoType(vtype) { - var goodType = videoContentTypesPrefixes.some(function(prefix) { - return vtype.indexOf(prefix) === 0; - }); - return goodType; - } - - function assetMsgKey(assetReq) { - var url = stripQueryParams(assetReq.url); - var key = assetReq.frameId + '-' + url; - return key; - } - - var PageNetworkTrafficCollector = function(tabId) { - this.tabId = tabId; - this.displayAdFound = false; - this.requests = {}; - this.msgsBeingSent = {}; - this.assetsSeen = {}; - this.allRedirects = {}; - }; - - var globalPageContainer = { - collectors: {}, - dyingCollectors: {}, - - cleanupCollector: function(tabId) { - if ( tabId in this.collectors ) { - delete globalPageContainer.collectors[tabId]; - } - }, - - onNewNavigation: function(details) { - var tabId = details.tabId; - this.cleanupCollector(tabId); - - ifTrackingEnabled( - details, - function() { - if ( !areListenersRegistered ) { - - registerListeners(); - } - this.collectors[tabId] = new PageNetworkTrafficCollector(tabId); - }.bind(this), - function() { - if ( areListenersRegistered ) { - - unregisterListeners(); - } - } - ); - }, - - onNavigationCommitted: function(details) { - - }, - - onNavigationCompleted: function(details) { - - }, - - onTabClose: function(tabId, closeInfo) { - - this.cleanupCollector(tabId); - delete this.collectors[tabId]; - }, - - onDisplayAdFound: function(tabId) { - this.collectors[tabId].displayAdFound = true; - }, - - getRandId: function() { - return String(Math.floor(Math.random() * 1e9)); - }, - - getCollector: function(tabId) { - if ( this.collectors.hasOwnProperty(tabId) ) { - return this.collectors[tabId]; - } - return null; - }, - - forwardCall: function(details, collectorMemberFunction) { - var collector = this.getCollector(details.tabId); - if ( collector !== null ) { - collectorMemberFunction.apply(collector, [details]); - } - } - }; - - PageNetworkTrafficCollector.prototype.sendLogMessageToTabConsole = function() { - var logMessage = Array.from(arguments).join(' '); - var message = {message: logMessage, event: 'console-log-message'}; - browserProxy.tabs.sendMessage(this.tabId, message); - }; - - PageNetworkTrafficCollector.prototype.sendToTab = function(assetReq, reqs, curPageUrl, adTrackingEvent) { - var msg = {}; - msg.assets = []; - msg.requests = []; - msg.event_data = {}; - msg.event = adTrackingEvent; - if ( adTrackingEvent === 'new-video-ad' ) { - msg.requests = reqs; - msg.requests.sort(function(reqA, reqB) {return reqA.requestTimestamp - reqB.requestTimestamp;}); - if ( assetReq ) { - msg.assets = [assetReq]; - } - } else if ( adTrackingEvent === 'new-invalid-video-ad' ) { - msg.requests = reqs.map(function(request) { - return parseHostnameFromUrl(request.url); - }); - msg.assets = [{ - - url: parseHostnameFromUrl(assetReq.url), - - contentType: assetReq.contentType, - size: assetReq.size - }]; - } - msg.origUrl = curPageUrl; - msg.displayAdFound = this.displayAdFound; - - browserProxy.tabs.sendMessage(this.tabId, msg); - }; - - PageNetworkTrafficCollector.prototype.getRedirKey = function(url, frameId) { - return url + ':' + frameId; - }; - - PageNetworkTrafficCollector.prototype.seenBefore = function(request) { - var oldTime = this.assetsSeen[assetMsgKey(request)]; - if ( oldTime && (request.requestTimestamp-oldTime < secBetweenDupAssets)){ - - return true; - } - return false; - }; - - PageNetworkTrafficCollector.prototype.recordSeenAsset = function(request) { - this.assetsSeen[assetMsgKey(request)] = request.requestTimestamp; - }; - - PageNetworkTrafficCollector.prototype.onBeforeRequest = function(details) { - var req = { - url: details.url, - type: details.type, - httpMethod: details.method, - frameId: details.frameId, - parentFrameId: details.parentFrameId, - requestTimestamp: details.timeStamp, - }; - this.requests[details.requestId] = req; - }; - - PageNetworkTrafficCollector.prototype.onSendHeaders = function(details) { - var request, header; - request = this.requests[details.requestId]; - header = request && findHeader(details.requestHeaders, 'x-requested-with'); - if ( header && header.value.toLowerCase().indexOf('flash') > -1 ) { - request.from_flash = true; - } - }; - - PageNetworkTrafficCollector.prototype.onHeadersReceived = function(details) { - var getFrameDetails = { - tabId: details.tabId, - processId: null, - frameId: details.frameId - }; - var pageNetworkTrafficController = this; - getFrame(getFrameDetails, function(frameDetails) { - if ( frameDetails && frameDetails.url ) { - pageNetworkTrafficController._onHeadersReceived(details, frameDetails); - } - }); - }; - - PageNetworkTrafficCollector.prototype._onHeadersReceived = function(details, frameDetails) { - var contentSize, contentRange; - - var request = this.requests[details.requestId]; - if ( request ) { - var redirParent = this.allRedirects[this.getRedirKey(details.url, details.frameId)]; - var header = request && findHeader(details.responseHeaders, 'content-type'); - var contentType = header && header.value.toLowerCase(); - - if ( contentType){ - request.contentType = contentType; - } - header = request && findHeader(details.responseHeaders, 'content-length'); - contentSize = header && header.value; - if ( contentSize ) { - request.size = request.size || 0; - request.size += parseInt(contentSize); - } - header = request && findHeader(details.responseHeaders, 'content-range'); - contentRange = header && header.value; - if ( contentRange ) { - request.contentRange = parseInt(contentRange.split('/')[1]); - } - - var frameUrl = null; - if ( frameDetails && frameDetails.url ) { - frameUrl = frameDetails.url; - } - if ( !this.bannedRequest(request) && - (this.isVideoReq(frameUrl, request) || (redirParent && redirParent.isVideo))) { - request.isVideo = true; - } - } - }; - - PageNetworkTrafficCollector.prototype.onBeforeRedirect = function(details) { - var request = this.requests[details.requestId]; - if ( request ) { - if ( request.redirects ) { - request.redirects.push(details.redirectUrl); - } else { - request.redirects = [details.redirectUrl]; - } - this.allRedirects[this.getRedirKey(details.redirectUrl, details.frameId)] = request; - } - }; - - PageNetworkTrafficCollector.prototype.isYoutubeMastheadRequest = function(url) { - var re = /video_masthead/; - return this.hasYoutubeDomain(url) && re.test(url); - }; - PageNetworkTrafficCollector.prototype.isYoutubeVideoRequest = function(srcUrl, destUrl) { - if ( !this.hasYoutubeDomain(srcUrl) ) { - return false; - } - - var re = /https?:\/\/r.*?\.googlevideo\.com\/videoplayback\?/; - return re.test(destUrl); - }; - PageNetworkTrafficCollector.prototype.processResponse = function(requestDetails, frameDetails) { - var request; - if ( requestDetails ) { - request = this.requests[requestDetails.requestId]; - if ( request ) { - request.responseStatus = requestDetails.statusCode; - request.responseTimestamp = requestDetails.timeStamp; - - var frameUrl = null; - if ( frameDetails && frameDetails.url ) { - frameUrl = frameDetails.url; - } - - var requestUrl = null; - if ( request.url ) { - requestUrl = request.url; - } - - if ( this.isYoutubeAdReq(frameUrl, requestUrl) ) { - var destVideoId = this.parseYoutubeVideoIdFromUrl(requestUrl); - var srcVideoId = this.parseYoutubeVideoIdFromUrl(frameUrl); - if ( srcVideoId && destVideoId ) { - request.isYoutubeAd = true; - request.isVideo = true; - request.rawSrcUrl = frameUrl; - request.rawDestUrl = requestUrl; - request.url = 'https://www.youtube.com/watch?v=' + this.parseYoutubeVideoIdFromUrl(requestUrl); - } - } else if ( !this.bannedRequest(request) && - (this.isVideo || this.isVideoReq(frameUrl, request))) { - request.isVideo = true; - } - - if ( request.isVideo ) { - - var msgKey = assetMsgKey(request); - this.msgsBeingSent[msgKey] = request; - if ( !this.seenBefore(request) ) { - this.sendMsgWhenQuiet(msgKey); - } - this.recordSeenAsset(request); - } - } - } - }; - - PageNetworkTrafficCollector.prototype.onResponseStarted = function(responseDetails) { - if ( responseDetails.frameId < 0 ) { - responseDetails.frameId = 99999; - - } - var getFrameDetails = { - tabId: responseDetails.tabId, - processId: null, - frameId: responseDetails.frameId - }; - var pageNetworkTrafficController = this; - getFrame(getFrameDetails, function(frameDetails) { - if ( frameDetails && frameDetails.url ) { - pageNetworkTrafficController.processResponse(responseDetails, frameDetails); - } - }); - }; - - PageNetworkTrafficCollector.prototype.hasBannedFiletype = function(request) { - var url = stripQueryParams(request.url); - if ( bannedFiletypesReg.exec(url) ) { - return true; - } else { - return false; - } - }; - - PageNetworkTrafficCollector.prototype.checkContentHeaders = function(request) { - if ( request.contentType && validVideoType(request.contentType) ) { - return true; - } - return false; - }; - - PageNetworkTrafficCollector.prototype.checkUrlExtension = function(request) { - var url = stripQueryParams(request.url); - if ( extensionsReg.exec(url) ) { - return true; - } else { - return false; - } - }; - - PageNetworkTrafficCollector.prototype.isVideoReq = function(srcUrl, request) { - if ( this.isYoutubeVideoRequest(srcUrl, request.url) ) { - return false; - } - return this.checkUrlExtension(request) || this.checkContentHeaders(request); - }; - PageNetworkTrafficCollector.prototype.hasYoutubeDomain = function(url) { - var hostname = parseHostnameFromUrl(url) ; - if ( hostname === 'www.youtube.com' ) { - return true; - } - return false; - }; - PageNetworkTrafficCollector.prototype.parseYoutubeVideoIdFromUrl = function(url) { - var re = /^https?:\/\/www\.youtube\.com\/get_video_info.*(?:\?|&)video_id=(.*?)(?:$|&)/; - var match = re.exec(url); - if ( match && match.length > 1 ) { - return match[1]; - } - - re = /^https?:\/\/www\.youtube\.com\/embed\/(.*?)(?:$|\?)/; - match = re.exec(url); - if ( match && match.length > 1 ) { - return match[1]; - } - - re = /^https?:\/\/www\.youtube\.com\/watch.*(\?|&)v=([^&]*)/; - match = re.exec(url); - if ( match && match.length > 1 ) { - return match[1]; - } - return null; - }; - - PageNetworkTrafficCollector.prototype.isYoutubeGetVideoInfoReq = function(url) { - var re = /^https?:\/\/www\.youtube\.com\/get_video_info\?/; - return re.test(url); - }; - PageNetworkTrafficCollector.prototype.isYoutubeAdReq = function(srcUrl, destUrl) { - - if ( !this.hasYoutubeDomain(srcUrl) || - !this.isYoutubeGetVideoInfoReq(destUrl)) { - return false; - } - if ( this.parseYoutubeVideoIdFromUrl(srcUrl) === - this.parseYoutubeVideoIdFromUrl(destUrl) && - !this.isYoutubeMastheadRequest(destUrl)) { - return false; - } - return true; - }; - - PageNetworkTrafficCollector.prototype.bannedRequest = function(request) { - return this.bannedVideoType(request) || this.hasBannedFiletype(request) || this.bannedVideoSize(request); - }; - - PageNetworkTrafficCollector.prototype.bannedVideoType = function(request) { - var badType = false; - if ( request.contentType ) { - badType = bannedContentTypes.some(function(prefix) { - return request.contentType.indexOf(prefix) >= 0; - }); - } - return badType; - }; - - PageNetworkTrafficCollector.prototype.bannedVideoSize = function(request) { - if ( request.size !== null ) { - if ( request.size < minVidSize || request.size > maxVidSize || request.contentRange > maxContentRange ) { - return true; - } - } - return false; - }; - - PageNetworkTrafficCollector.prototype.grabTagReqs = function(tabRequests, assetRequest) { - var minTimestamp, maxTimestamp; - minTimestamp = assetRequest.requestTimestamp - secBefore; - maxTimestamp = assetRequest.requestTimestamp + secAfter; - - var filteredRequests = tabRequests.filter(function(request) { - return (request.requestTimestamp > minTimestamp && - request.requestTimestamp < maxTimestamp && - request.frameId === assetRequest.frameId && - request.url !== assetRequest.url && - (hasValidRequestType(request) || - isPixelRequest(request))); - }); - - return filteredRequests; - }; - - PageNetworkTrafficCollector.prototype.isValidVideoAd = function(assetRequest, tagRequests) { - var hasVpaidOrVastRequest = tagRequests.some(function(tagRequest) { - return isVpaidOrVastRequest(tagRequest); - }); - - if ( assetRequest.isYoutubeAd ) { - return true; - } - if ( hasVpaidOrVastRequest ) { - return true; - } - var hasTopVideoAssetDomain = topVideoAssetDomains.some(function(assetDomain) { - return hasDomain(assetRequest.url, assetDomain); - }); - - return hasTopVideoAssetDomain; - }; - - PageNetworkTrafficCollector.prototype.sendMsgWhenQuiet = function(msgKey) { - var _this = this, - origPageUrl, msgAssetReq; - msgAssetReq = this.msgsBeingSent[msgKey]; - browserProxy.tabs.get(this.tabId, function(tab) {origPageUrl = tab.url;}); - - setTimeout(function() { - var rawRequests = []; - if ( globalPageContainer.collectors[_this.tabId] === _this ) { - for ( var reqId in _this.requests ) { - rawRequests.push(_this.requests[reqId]); - } - var tagReqs = _this.grabTagReqs(rawRequests, msgAssetReq); - - if ( _this.isValidVideoAd(msgAssetReq, tagReqs) ) { - _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-video-ad'); - } else { - - _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-invalid-video-ad'); - } - - } else { - - } - delete _this.msgsBeingSent[msgKey]; - }, secAfter+secBefore); - }; - - PageNetworkTrafficCollector.prototype.existingMessage = function(candidateRequest) { - var frameMsg = this.msgsBeingSent[candidateRequest.frameId]; - if ( frameMsg ) { - return frameMsg; - } else { - return null; - } - }; - - function onBeforeRequestListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRequest); - } - - function onSendHeadersListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onSendHeaders); - } - - function onHeadersReceivedListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onHeadersReceived); - } - - function onBeforeRedirectListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRedirect); - } - - function onResponseStartedListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onResponseStarted); - } - - function onCommittedListener(details) { - if ( details.frameId === 0 ) { - globalPageContainer.onNavigationCommitted(details); - } - } - - function onCompletedListener(details) { - if ( details.frameId === 0 ) { - globalPageContainer.onNavigationCompleted(details); - } - } - - function onRemovedListener(tabId, closeInfo) { - globalPageContainer.onTabClose(tabId, closeInfo); - } - - function onMessageListener(message, sender, sendResponse) { - if ( message.event === 'new-ad' && message.data.event === 'ad' ) { - var tabId = sender.tab.id; - if ( tabId ) { - globalPageContainer.onDisplayAdFound(tabId); - } - } - } - - function registerListeners() { - - browserProxy.webRequest.onBeforeRequest.addListener( - onBeforeRequestListener, - {urls: ['http://*/*', 'https://*/*']}, - [] - ); - - browserProxy.webRequest.onSendHeaders.addListener( - onSendHeadersListener, - {urls: ['http://*/*', 'https://*/*']}, - ['requestHeaders'] - ); - - browserProxy.webRequest.onHeadersReceived.addListener( - onHeadersReceivedListener, - {urls: ['http://*/*', 'https://*/*']}, - ['responseHeaders'] - ); - - browserProxy.webRequest.onBeforeRedirect.addListener( - onBeforeRedirectListener, - {urls: ['http://*/*', 'https://*/*']}, - [] - ); - - browserProxy.webRequest.onResponseStarted.addListener( - onResponseStartedListener, - {urls: ['http://*/*', 'https://*/*']}, - ['responseHeaders'] - ); - - browserProxy.webNavigation.onCommitted.addListener(onCommittedListener); - browserProxy.webNavigation.onCompleted.addListener(onCompletedListener); - browserProxy.tabs.onRemoved.addListener(onRemovedListener); - browserProxy.runtime.onMessage.addListener(onMessageListener); - - areListenersRegistered = true; - } - - function unregisterListeners() { - - browserProxy.webRequest.onBeforeRequest.removeListener( - onBeforeRequestListener - ); - - browserProxy.webRequest.onSendHeaders.removeListener( - onSendHeadersListener - ); - - browserProxy.webRequest.onHeadersReceived.removeListener( - onHeadersReceivedListener - ); - - browserProxy.webRequest.onBeforeRedirect.removeListener( - onBeforeRedirectListener - ); - - browserProxy.webRequest.onResponseStarted.removeListener( - onResponseStartedListener - ); - - browserProxy.webNavigation.onCommitted.removeListener(onCommittedListener); - browserProxy.webNavigation.onCompleted.removeListener(onCompletedListener); - browserProxy.tabs.onRemoved.removeListener(onRemovedListener); - browserProxy.runtime.onMessage.removeListener(onMessageListener); - - areListenersRegistered = false; - } - - function areRequiredBrowserApisAvailable() { - return requiredBrowserApis.every(function(api) { - return typeof api !== 'undefined'; - }); - } - - if ( areRequiredBrowserApisAvailable() ) { - ifBrowserValid( - function() { - browserProxy.webNavigation.onBeforeNavigate.addListener( - function(details) { - if ( details.frameId === 0 ) { - globalPageContainer.onNewNavigation(details); - } - }, - { - url: [{urlMatches: 'http://*/*'}, {urlMatches: 'https://*/*'}] - } - ); - }, function() { - - } - ); - } - - browserProxy.runtime.onMessage.addListener(function(request, sender, sendResponse) { - if ( request === 'is_browser_valid' ) { - ifBrowserValid( - sendResponse({'browser_valid': true}), - sendResponse({'browser_valid': false}) - ); - } - }); - - browserProxy.runtime.onMessage.addListener(function(request, sender, sendResponse) { - if ( request === 'is_tracking_enabled' ) { - ifTrackingEnabled( - sender.tab, - function() { - try {sendResponse({'tracking_enabled': true});} - catch(err) {} }, - function() { - try {sendResponse({'tracking_enabled': false});} - catch(err) {}} - ); - } - return true; - }); - -})(); diff --git a/src/drivers/webextension/js/options.js b/src/drivers/webextension/js/options.js index 4c5d7cc67..935d11318 100644 --- a/src/drivers/webextension/js/options.js +++ b/src/drivers/webextension/js/options.js @@ -5,23 +5,23 @@ const wappalyzer = new Wappalyzer(); function getOption(name, defaultValue, callback) { browser.storage.local.get(name) - .then(item => { + .then((item) => { callback(item.hasOwnProperty(name) ? item[name] : defaultValue); }); } function setOption(name, value) { - ( chrome || browser ).runtime.sendMessage({ + (chrome || browser).runtime.sendMessage({ id: 'set_option', key: name, - value: value + value, }); } document.addEventListener('DOMContentLoaded', () => { - var nodes = document.querySelectorAll('[data-i18n]'); + const nodes = document.querySelectorAll('[data-i18n]'); - Array.prototype.forEach.call(nodes, node => { + Array.prototype.forEach.call(nodes, (node) => { node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n); }); @@ -37,7 +37,7 @@ document.addEventListener('DOMContentLoaded', () => { open(wappalyzer.config.websiteURL); }); - getOption('upgradeMessage', true, value => { + getOption('upgradeMessage', true, (value) => { const el = document.querySelector('#option-upgrade-message'); el.checked = value; @@ -47,7 +47,7 @@ document.addEventListener('DOMContentLoaded', () => { }); }); - getOption('dynamicIcon', true, value => { + getOption('dynamicIcon', true, (value) => { const el = document.querySelector('#option-dynamic-icon'); el.checked = value; @@ -57,7 +57,7 @@ document.addEventListener('DOMContentLoaded', () => { }); }); - getOption('tracking', true, value => { + getOption('tracking', true, (value) => { const el = document.querySelector('#option-tracking'); el.checked = value; diff --git a/src/drivers/webextension/js/popup.js b/src/drivers/webextension/js/popup.js index e846dd0f5..e560162fd 100644 --- a/src/drivers/webextension/js/popup.js +++ b/src/drivers/webextension/js/popup.js @@ -1,14 +1,14 @@ /** global: chrome */ /** global: browser */ -var pinnedCategory = null; +let pinnedCategory = null; -var func = tabs => { - ( chrome || browser ).runtime.sendMessage({ +const func = (tabs) => { + (chrome || browser).runtime.sendMessage({ id: 'get_apps', tab: tabs[0], - source: 'popup.js' - }, response => { + source: 'popup.js', + }, (response) => { pinnedCategory = response.pinnedCategory; replaceDomWhenReady(appsToDomTemplate(response)); @@ -20,7 +20,7 @@ browser.tabs.query({ active: true, currentWindow: true }) .catch(console.error); function replaceDomWhenReady(dom) { - if ( /complete|interactive|loaded/.test(document.readyState) ) { + if (/complete|interactive|loaded/.test(document.readyState)) { replaceDom(dom); } else { document.addEventListener('DOMContentLoaded', () => { @@ -30,32 +30,32 @@ function replaceDomWhenReady(dom) { } function replaceDom(domTemplate) { - var container = document.getElementsByClassName('container')[0]; + const container = document.getElementsByClassName('container')[0]; - while ( container.firstChild ) { + while (container.firstChild) { container.removeChild(container.firstChild); } container.appendChild(jsonToDOM(domTemplate, document, {})); - var nodes = document.querySelectorAll('[data-i18n]'); + const nodes = document.querySelectorAll('[data-i18n]'); - Array.prototype.forEach.call(nodes, node => { + Array.prototype.forEach.call(nodes, (node) => { node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n); }); - Array.from(document.querySelectorAll('.detected__category-pin-wrapper')).forEach(pin => { + Array.from(document.querySelectorAll('.detected__category-pin-wrapper')).forEach((pin) => { pin.addEventListener('click', () => { const categoryId = parseInt(pin.dataset.categoryId, 10); - if ( categoryId === pinnedCategory ) { + if (categoryId === pinnedCategory) { pin.className = 'detected__category-pin-wrapper'; pinnedCategory = null; } else { const active = document.querySelector('.detected__category-pin-wrapper--active'); - if ( active ) { + if (active) { active.className = 'detected__category-pin-wrapper'; } @@ -64,7 +64,7 @@ function replaceDom(domTemplate) { pinnedCategory = categoryId; } - ( chrome || browser ).runtime.sendMessage({ + (chrome || browser).runtime.sendMessage({ id: 'set_option', key: 'pinnedCategory', value: pinnedCategory, @@ -74,115 +74,115 @@ function replaceDom(domTemplate) { } function appsToDomTemplate(response) { - let template = []; + let template = []; - if ( response.tabCache && Object.keys(response.tabCache.detected).length > 0 ) { + if (response.tabCache && Object.keys(response.tabCache.detected).length > 0) { const categories = {}; // Group apps by category - for ( let appName in response.tabCache.detected ) { - response.apps[appName].cats.forEach(cat => { + for (const appName in response.tabCache.detected) { + response.apps[appName].cats.forEach((cat) => { categories[cat] = categories[cat] || { apps: [] }; categories[cat].apps[appName] = appName; }); } - for ( let cat in categories ) { + for (const cat in categories) { const apps = []; - for ( let appName in categories[cat].apps ) { - let confidence = response.tabCache.detected[appName].confidenceTotal; - let version = response.tabCache.detected[appName].version; + for (const appName in categories[cat].apps) { + const confidence = response.tabCache.detected[appName].confidenceTotal; + const version = response.tabCache.detected[appName].version; apps.push( [ 'a', { class: 'detected__app', target: '_blank', - href: 'https://www.wappalyzer.com/technologies/' + slugify(appName) + href: `https://www.wappalyzer.com/technologies/${slugify(appName)}`, }, [ 'img', { class: 'detected__app-icon', - src: '../images/icons/' + (response.apps[appName].icon || 'default.svg') + src: `../images/icons/${response.apps[appName].icon || 'default.svg'}`, }, ], [ 'span', { - class: 'detected__app-name' + class: 'detected__app-name', }, appName, ], version ? [ 'span', { - class: 'detected__app-version' + class: 'detected__app-version', }, - version + version, ] : null, confidence < 100 ? [ 'span', { - class: 'detected__app-confidence' + class: 'detected__app-confidence', }, - confidence + '% sure' - ] : null - ] + `${confidence}% sure`, + ] : null, + ], ); } template.push( [ 'div', { - class: 'detected__category' + class: 'detected__category', }, [ 'div', { - class: 'detected__category-name' + class: 'detected__category-name', }, [ 'a', { class: 'detected__category-link', target: '_blank', - href: 'https://www.wappalyzer.com/categories/' + slugify(response.categories[cat].name) + href: `https://www.wappalyzer.com/categories/${slugify(response.categories[cat].name)}`, }, - browser.i18n.getMessage('categoryName' + cat), + browser.i18n.getMessage(`categoryName${cat}`), ], [ 'span', { - class: 'detected__category-pin-wrapper' + ( pinnedCategory == cat ? ' detected__category-pin-wrapper--active' : '' ), + class: `detected__category-pin-wrapper${pinnedCategory == cat ? ' detected__category-pin-wrapper--active' : ''}`, 'data-category-id': cat, - 'title': browser.i18n.getMessage('categoryPin'), + title: browser.i18n.getMessage('categoryPin'), }, [ 'img', { class: 'detected__category-pin detected__category-pin--active', - src: '../images/pin-active.svg' + src: '../images/pin-active.svg', }, ], [ 'img', { class: 'detected__category-pin detected__category-pin--inactive', - src: '../images/pin.svg' - } - ] - ] + src: '../images/pin.svg', + }, + ], + ], ], [ 'div', { - class: 'detected__apps' + class: 'detected__apps', }, - apps - ] - ] + apps, + ], + ], ); } template = [ 'div', { - class: 'detected' + class: 'detected', }, - template + template, ]; } else { template = [ 'div', { - class: 'empty' + class: 'empty', }, [ 'span', { - class: 'empty__text' + class: 'empty__text', }, - browser.i18n.getMessage('noAppsDetected') + browser.i18n.getMessage('noAppsDetected'), ], ]; } diff --git a/src/drivers/webextension/manifest.json b/src/drivers/webextension/manifest.json index 2880e3aea..4164e1195 100644 --- a/src/drivers/webextension/manifest.json +++ b/src/drivers/webextension/manifest.json @@ -51,7 +51,7 @@ "https://www.alphaecommerce.gr/*" ], "js": [ - "js/iframe.js" + "lib/js/iframe.js" ], "run_at": "document_start", "all_frames": true diff --git a/src/wappalyzer.js b/src/wappalyzer.js index e117c6121..b83240533 100644 --- a/src/wappalyzer.js +++ b/src/wappalyzer.js @@ -6,13 +6,116 @@ * License: GPLv3 http://www.gnu.org/licenses/gpl-3.0.txt */ -'use strict'; - const validation = { hostname: /(www.)?((.+?)\.(([a-z]{2,3}\.)?[a-z]{2,6}))$/, - hostnameBlacklist: /((local|dev(elopment)?|stag(e|ing)?|test(ing)?|demo(shop)?|admin|google|cache)\.|\/admin|\.local)/ + hostnameBlacklist: /((local|dev(elopment)?|stag(e|ing)?|test(ing)?|demo(shop)?|admin|google|cache)\.|\/admin|\.local)/, }; +/** + * Enclose string in array + */ +function asArray(value) { + return value instanceof Array ? value : [value]; +} + +/** + * + */ +function asyncForEach(iterable, iterator) { + return Promise.all((iterable || []) + .map(item => new Promise(resolve => setTimeout(() => resolve(iterator(item)), 1)))); +} + +/** + * Mark application as detected, set confidence and version + */ +function addDetected(app, pattern, type, value, key) { + app.detected = true; + + // Set confidence level + app.confidence[`${type} ${key ? `${key} ` : ''}${pattern.regex}`] = pattern.confidence === undefined ? 100 : parseInt(pattern.confidence, 10); + + // Detect version number + if (pattern.version) { + const versions = []; + const matches = pattern.regex.exec(value); + + let { version } = pattern; + + if (matches) { + matches.forEach((match, i) => { + // Parse ternary operator + const ternary = new RegExp(`\\\\${i}\\?([^:]+):(.*)$`).exec(version); + + if (ternary && ternary.length === 3) { + version = version.replace(ternary[0], match ? ternary[1] : ternary[2]); + } + + // Replace back references + version = version.trim().replace(new RegExp(`\\\\${i}`, 'g'), match || ''); + }); + + if (version && versions.indexOf(version) === -1) { + versions.push(version); + } + + if (versions.length) { + // Use the longest detected version number + app.version = versions.reduce((a, b) => (a.length > b.length ? a : b)); + } + } + } +} + +function resolveExcludes(apps, detected) { + const excludes = []; + + // Exclude app in detected apps only + Object.keys(Object.assign({}, apps, detected)).forEach((appName) => { + const app = apps[appName]; + + if (app.props.excludes) { + asArray(app.props.excludes).forEach((excluded) => { + excludes.push(excluded); + }); + } + }); + + // Remove excluded applications + Object.keys(apps).forEach((appName) => { + if (excludes.indexOf(appName) > -1) { + delete apps[appName]; + } + }); +} + +class Application { + constructor(name, props, detected) { + this.confidence = {}; + this.confidenceTotal = 0; + this.detected = Boolean(detected); + this.excludes = []; + this.name = name; + this.props = props; + this.version = ''; + } + + /** + * Calculate confidence total + */ + getConfidence() { + let total = 0; + + Object.values(this.confidence).forEach((id) => { + total += this.confidence[id]; + }); + + this.confidenceTotal = Math.min(total, 100); + + return this.confidenceTotal; + } +} + class Wappalyzer { constructor() { this.apps = {}; @@ -37,27 +140,29 @@ class Wappalyzer { this.driver.log(message, source || '', type || 'debug'); } - asyncForEach(iterable, iterator) { - return Promise.all(( iterable || [] ).map(item => new Promise(resolve => setTimeout(() => resolve(iterator(item)), 1)))); - } - analyze(url, data, context) { - const startTime = new Date(); - + const apps = {}; const promises = []; + const startTime = new Date(); + const { + scripts, + cookies, + headers, + js, + } = data; - var apps = {}; + let { html } = data; - if ( this.detected[url.canonical] === undefined ) { + if (this.detected[url.canonical] === undefined) { this.detected[url.canonical] = {}; } // Additional information - var language = null; + let language = null; - if ( data.html ) { - if ( typeof data.html !== 'string' ) { - data.html = ''; + if (html) { + if (typeof html !== 'string') { + html = ''; } const matches = data.html.match(/]*[: ]lang="([a-z]{2}((-|_)[A-Z]{2})?)"/i); @@ -65,60 +170,62 @@ class Wappalyzer { language = matches && matches.length ? matches[1] : null; } - Object.keys(this.apps).forEach(appName => { - apps[appName] = this.detected[url.canonical] && this.detected[url.canonical][appName] ? this.detected[url.canonical][appName] : new Application(appName, this.apps[appName]); + Object.keys(this.apps).forEach((appName) => { + apps[appName] = this.detected[url.canonical] && this.detected[url.canonical][appName] + ? this.detected[url.canonical][appName] + : new Application(appName, this.apps[appName]); - var app = apps[appName]; + const app = apps[appName]; this.analyzeUrl(app, url); - if ( data.html ) { - promises.push(this.analyzeHtml(app, data.html)); - promises.push(this.analyzeMeta(app, data.html)); + if (html) { + promises.push(this.analyzeHtml(app, html)); + promises.push(this.analyzeMeta(app, html)); } - if ( data.scripts ) { - promises.push(this.analyzeScripts(app, data.scripts)); + if (scripts) { + promises.push(this.analyzeScripts(app, scripts)); } - if ( data.cookies ) { - promises.push(this.analyzeCookies(app, data.cookies)); + if (cookies) { + promises.push(this.analyzeCookies(app, cookies)); } - if ( data.headers ) { - promises.push(this.analyzeHeaders(app, data.headers)); + if (headers) { + promises.push(this.analyzeHeaders(app, headers)); } }); - if ( data.js ) { - Object.keys(data.js).forEach(appName => { - if (typeof data.js[appName] != 'function') { - promises.push(this.analyzeJs(apps[appName], data.js[appName])); + if (js) { + Object.keys(js).forEach((appName) => { + if (typeof js[appName] !== 'function') { + promises.push(this.analyzeJs(apps[appName], js[appName])); } }); } - return new Promise(resolve => { + return new Promise((resolve) => { Promise.all(promises) .then(() => { - Object.keys(apps).forEach(appName => { - let app = apps[appName]; + Object.keys(apps).forEach((appName) => { + const app = apps[appName]; if (!app.detected || !app.getConfidence()) { delete apps[app.name]; } }); - this.resolveExcludes(apps, this.detected[url]); + resolveExcludes(apps, this.detected[url]); this.resolveImplies(apps, url.canonical); this.cacheDetectedApps(apps, url.canonical); this.trackDetectedApps(apps, url, language); - this.log('Processing ' + Object.keys(data).join(', ') + ' took ' + (( new Date() - startTime ) / 1000).toFixed(2) + 's (' + url.hostname + ')', 'core'); + this.log(`Processing ${Object.keys(data).join(', ')} took ${((new Date() - startTime) / 1000).toFixed(2)}s (${url.hostname})`, 'core'); - if ( Object.keys(apps).length ) { - this.log('Identified ' + Object.keys(apps).join(', ') + ' (' + url.hostname + ')', 'core'); + if (Object.keys(apps).length) { + this.log(`Identified ${Object.keys(apps).join(', ')} (${url.hostname})`, 'core'); } this.driver.displayApps(this.detected[url.canonical], { language }, context); @@ -140,32 +247,34 @@ class Wappalyzer { */ robotsTxtAllows(url) { return new Promise((resolve, reject) => { - var parsed = this.parseUrl(url); + const parsed = this.parseUrl(url); - if ( parsed.protocol !== 'http:' && parsed.protocol !== 'https:' ) { - return reject(); + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + reject(); + + return; } this.driver.getRobotsTxt(parsed.host, parsed.protocol === 'https:') - .then(robotsTxt => { - if ( robotsTxt.some(disallowedPath => parsed.pathname.indexOf(disallowedPath) === 0) ) { + .then((robotsTxt) => { + if (robotsTxt.some(disallowedPath => parsed.pathname.indexOf(disallowedPath) === 0)) { return reject(); } return resolve(); }, () => resolve()); }); - }; + } /** * Parse a URL */ parseUrl(url) { - var a = this.driver.document.createElement('a'); + const a = this.driver.document.createElement('a'); a.href = url; - a.canonical = a.protocol + '//' + a.host + a.pathname; + a.canonical = `${a.protocol}//${a.host}${a.pathname}`; return a; } @@ -173,22 +282,21 @@ class Wappalyzer { /** * */ - parseRobotsTxt(robotsTxt) { - var userAgent; - var disallow = []; + static parseRobotsTxt(robotsTxt) { + const disallow = []; - robotsTxt.split('\n').forEach(line => { - var matches = /^User-agent:\s*(.+)$/i.exec(line); + let userAgent; - if ( matches ) { + robotsTxt.split('\n').forEach((line) => { + let matches = /^User-agent:\s*(.+)$/i.exec(line); + + if (matches) { userAgent = matches[1].toLowerCase(); - } else { - if ( userAgent === '*' || userAgent === 'wappalyzer' ) { - matches = /^Disallow:\s*(.+)$/i.exec(line); + } else if (userAgent === '*' || userAgent === 'wappalyzer') { + matches = /^Disallow:\s*(.+)$/i.exec(line); - if ( matches ) { - disallow.push(matches[1]); - } + if (matches) { + disallow.push(matches[1]); } } }); @@ -200,66 +308,59 @@ class Wappalyzer { * */ ping() { - if ( Object.keys(this.hostnameCache).length > 100 ) { + if (Object.keys(this.hostnameCache).length > 100) { this.driver.ping(this.hostnameCache); this.hostnameCache = {}; } - if ( this.adCache.length > 50 ) { + if (this.adCache.length > 50) { this.driver.ping({}, this.adCache); this.adCache = []; } } - /** - * Enclose string in array - */ - asArray(value) { - return value instanceof Array ? value : [ value ]; - } - /** * Parse apps.json patterns */ parsePatterns(patterns) { - if ( !patterns ) { + if (!patterns) { return []; } - var parsed = {}; + let parsed = {}; // Convert string to object containing array containing string - if ( typeof patterns === 'string' || patterns instanceof Array ) { + if (typeof patterns === 'string' || patterns instanceof Array) { patterns = { - main: this.asArray(patterns) + main: asArray(patterns), }; } - Object.keys(patterns).forEach(key => { + Object.keys(patterns).forEach((key) => { parsed[key] = []; - this.asArray(patterns[key]).forEach(pattern => { - var attrs = {}; + asArray(patterns[key]).forEach((pattern) => { + const attrs = {}; pattern.split('\\;').forEach((attr, i) => { - if ( i ) { + if (i) { // Key value pairs attr = attr.split(':'); - if ( attr.length > 1 ) { + if (attr.length > 1) { attrs[attr.shift()] = attr.join(':'); } } else { attrs.string = attr; try { - attrs.regex = new RegExp(attr.replace('/', '\/'), 'i'); // Escape slashes in regular expression + attrs.regex = new RegExp(attr.replace('/', '\\/'), 'i'); // Escape slashes in regular expression } catch (e) { attrs.regex = new RegExp(); - this.log(e + ': ' + attr, 'error', 'core'); + this.log(`${e}: ${attr}`, 'error', 'core'); } } }); @@ -269,7 +370,7 @@ class Wappalyzer { }); // Convert back to array if the original pattern list was an array (or string) - if ( 'main' in parsed ) { + if ('main' in parsed) { parsed = parsed.main; } @@ -280,65 +381,45 @@ class Wappalyzer { * Parse JavaScript patterns */ parseJsPatterns() { - Object.keys(this.apps).forEach(appName => { + Object.keys(this.apps).forEach((appName) => { if (this.apps[appName].js) { this.jsPatterns[appName] = this.parsePatterns(this.apps[appName].js); } }); } - resolveExcludes(apps, detected) { - let excludes = []; - - // Exclude app in detected apps only - Object.keys(Object.assign({}, apps, detected)).forEach(appName => { - let app = apps[appName]; - - if (app.props.excludes) { - this.asArray(app.props.excludes).forEach(excluded => { - excludes.push(excluded); - }); - } - }); - - // Remove excluded applications - Object.keys(apps).forEach(appName => { - if (excludes.indexOf(appName) > -1) { - delete apps[appName]; - } - }); - } - resolveImplies(apps, url) { - var checkImplies = true; + let checkImplies = true; // Implied applications // Run several passes as implied apps may imply other apps - while ( checkImplies ) { + while (checkImplies) { checkImplies = false; - Object.keys(apps).forEach(appName => { - var app = apps[appName]; + Object.keys(apps).forEach((appName) => { + const app = apps[appName]; - if ( app && app.props.implies ) { - this.asArray(app.props.implies).forEach(implied => { - implied = this.parsePatterns(implied)[0]; + if (app && app.props.implies) { + asArray(app.props.implies).forEach((implied) => { + [implied] = this.parsePatterns(implied); - if ( !this.apps[implied.string] ) { - this.log('Implied application ' + implied.string + ' does not exist', 'core', 'warn'); + if (!this.apps[implied.string]) { + this.log(`Implied application ${implied.string} does not exist`, 'core', 'warn'); return; } - if ( !( implied.string in apps ) ) { - apps[implied.string] = this.detected[url] && this.detected[url][implied.string] ? this.detected[url][implied.string] : new Application(implied.string, this.apps[implied.string], true); + if (!(implied.string in apps)) { + apps[implied.string] = this.detected[url] && this.detected[url][implied.string] + ? this.detected[url][implied.string] + : new Application(implied.string, this.apps[implied.string], true); checkImplies = true; } // Apply app confidence to implied app - Object.keys(app.confidence).forEach(id => { - apps[implied.string].confidence[id + ' implied by ' + appName] = app.confidence[id] * ( implied.confidence === undefined ? 1 : implied.confidence / 100 ); + Object.keys(app.confidence).forEach((id) => { + apps[implied.string].confidence[`${id} implied by ${appName}`] = app.confidence[id] * (implied.confidence === undefined ? 1 : implied.confidence / 100); }); }); } @@ -350,14 +431,17 @@ class Wappalyzer { * Cache detected applications */ cacheDetectedApps(apps, url) { - Object.keys(apps).forEach(appName => { - let app = apps[appName]; + Object.keys(apps).forEach((appName) => { + const app = apps[appName]; // Per URL this.detected[url][appName] = app; - Object.keys(app.confidence).forEach(id => this.detected[url][appName].confidence[id] = app.confidence[id]); - }) + Object.keys(app.confidence) + .forEach((id) => { + this.detected[url][appName].confidence[id] = app.confidence[id]; + }); + }); if (this.driver.ping instanceof Function) { this.ping(); @@ -368,41 +452,44 @@ class Wappalyzer { * Track detected applications */ trackDetectedApps(apps, url, language) { - if ( !( this.driver.ping instanceof Function ) ) { + if (!(this.driver.ping instanceof Function)) { return; } - const hostname = url.protocol + '//' + url.hostname; + const hostname = `${url.protocol}//${url.hostname}`; - Object.keys(apps).forEach(appName => { + Object.keys(apps).forEach((appName) => { const app = apps[appName]; - if ( this.detected[url.canonical][appName].getConfidence() >= 100 ) { - if ( validation.hostname.test(url.hostname) && !validation.hostnameBlacklist.test(url.hostname) ) { - if ( !( hostname in this.hostnameCache ) ) { + if (this.detected[url.canonical][appName].getConfidence() >= 100) { + if ( + validation.hostname.test(url.hostname) + && !validation.hostnameBlacklist.test(url.hostname) + ) { + if (!(hostname in this.hostnameCache)) { this.hostnameCache[hostname] = { applications: {}, - meta: {} + meta: {}, }; } - if ( !( appName in this.hostnameCache[hostname].applications ) ) { + if (!(appName in this.hostnameCache[hostname].applications)) { this.hostnameCache[hostname].applications[appName] = { - hits: 0 + hits: 0, }; } - this.hostnameCache[hostname].applications[appName].hits ++; + this.hostnameCache[hostname].applications[appName].hits += 1; - if ( apps[appName].version ) { + if (apps[appName].version) { this.hostnameCache[hostname].applications[appName].version = app.version; } } } }); - if ( hostname in this.hostnameCache ) { - this.hostnameCache[hostname].meta['language'] = language; + if (hostname in this.hostnameCache) { + this.hostnameCache[hostname].meta.language = language; } this.ping(); @@ -412,15 +499,15 @@ class Wappalyzer { * Analyze URL */ analyzeUrl(app, url) { - var patterns = this.parsePatterns(app.props.url); + const patterns = this.parsePatterns(app.props.url); - if ( !patterns.length ) { + if (!patterns.length) { return Promise.resolve(); } - return this.asyncForEach(patterns, pattern => { - if ( pattern.regex.test(url.canonical) ) { - this.addDetected(app, pattern, 'url', url.canonical); + return asyncForEach(patterns, (pattern) => { + if (pattern.regex.test(url.canonical)) { + addDetected(app, pattern, 'url', url.canonical); } }); } @@ -429,15 +516,15 @@ class Wappalyzer { * Analyze HTML */ analyzeHtml(app, html) { - var patterns = this.parsePatterns(app.props.html); + const patterns = this.parsePatterns(app.props.html); - if ( !patterns.length ) { + if (!patterns.length) { return Promise.resolve(); } - return this.asyncForEach(patterns, pattern => { - if ( pattern.regex.test(html) ) { - this.addDetected(app, pattern, 'html', html); + return asyncForEach(patterns, (pattern) => { + if (pattern.regex.test(html)) { + addDetected(app, pattern, 'html', html); } }); } @@ -446,16 +533,16 @@ class Wappalyzer { * Analyze script tag */ analyzeScripts(app, scripts) { - var patterns = this.parsePatterns(app.props.script); + const patterns = this.parsePatterns(app.props.script); - if ( !patterns.length ) { + if (!patterns.length) { return Promise.resolve(); } - return this.asyncForEach(patterns, pattern => { - scripts.forEach(uri => { - if ( pattern.regex.test(uri) ) { - this.addDetected(app, pattern, 'script', uri); + return asyncForEach(patterns, (pattern) => { + scripts.forEach((uri) => { + if (pattern.regex.test(uri)) { + addDetected(app, pattern, 'script', uri); } }); }); @@ -471,20 +558,20 @@ class Wappalyzer { let matches; - while ( patterns && ( matches = regex.exec(html) ) ) { - for ( let meta in patterns ) { - const r = new RegExp('(?:name|property)=["\']' + meta + '["\']', 'i'); + while (patterns && (matches = regex.exec(html))) { + patterns.forEach((meta) => { + const r = new RegExp(`(?:name|property)=["']${meta}["']`, 'i'); - if ( r.test(matches[0]) ) { - let content = matches[0].match(/content=("|')([^"']+)("|')/i); + if (r.test(matches[0])) { + const content = matches[0].match(/content=("|')([^"']+)("|')/i); - promises.push(this.asyncForEach(patterns[meta], pattern => { - if ( content && content.length === 4 && pattern.regex.test(content[2]) ) { - this.addDetected(app, pattern, 'meta', content[2], meta); + promises.push(asyncForEach(patterns[meta], (pattern) => { + if (content && content.length === 4 && pattern.regex.test(content[2])) { + addDetected(app, pattern, 'meta', content[2], meta); } })); } - } + }); } return promises ? Promise.all(promises) : Promise.resolve(); @@ -497,15 +584,15 @@ class Wappalyzer { const patterns = this.parsePatterns(app.props.headers); const promises = []; - Object.keys(patterns).forEach(headerName => { - if (typeof patterns[headerName] != 'function') { - promises.push(this.asyncForEach(patterns[headerName], pattern => { + Object.keys(patterns).forEach((headerName) => { + if (typeof patterns[headerName] !== 'function') { + promises.push(asyncForEach(patterns[headerName], (pattern) => { headerName = headerName.toLowerCase(); - if ( headerName in headers ) { - headers[headerName].forEach(headerValue => { - if ( pattern.regex.test(headerValue) ) { - this.addDetected(app, pattern, 'headers', headerValue, headerName); + if (headerName in headers) { + headers[headerName].forEach((headerValue) => { + if (pattern.regex.test(headerValue)) { + addDetected(app, pattern, 'headers', headerValue, headerName); } }); } @@ -523,15 +610,15 @@ class Wappalyzer { const patterns = this.parsePatterns(app.props.cookies); const promises = []; - Object.keys(patterns).forEach(cookieName => { - if (typeof patterns[cookieName] != 'function') { + Object.keys(patterns).forEach((cookieName) => { + if (typeof patterns[cookieName] !== 'function') { cookieName = cookieName.toLowerCase(); - promises.push(this.asyncForEach(patterns[cookieName], pattern => { - const cookie = cookies.find(cookie => cookie.name.toLowerCase() === cookieName); + promises.push(asyncForEach(patterns[cookieName], (pattern) => { + const cookie = cookies.find(_cookie => _cookie.name.toLowerCase() === cookieName); - if ( cookie && pattern.regex.test(cookie.value) ) { - this.addDetected(app, pattern, 'cookies', cookie.value, cookieName); + if (cookie && pattern.regex.test(cookie.value)) { + addDetected(app, pattern, 'cookies', cookie.value, cookieName); } })); } @@ -546,14 +633,14 @@ class Wappalyzer { analyzeJs(app, results) { const promises = []; - Object.keys(results).forEach(string => { - if (typeof results[string] != 'function') { - promises.push(this.asyncForEach(Object.keys(results[string]), index => { + Object.keys(results).forEach((string) => { + if (typeof results[string] !== 'function') { + promises.push(asyncForEach(Object.keys(results[string]), (index) => { const pattern = this.jsPatterns[app.name][string][index]; const value = results[string][index]; - if ( pattern && pattern.regex.test(value) ) { - this.addDetected(app, pattern, 'js', value); + if (pattern && pattern.regex.test(value)) { + addDetected(app, pattern, 'js', value); } })); } @@ -561,76 +648,8 @@ class Wappalyzer { return promises ? Promise.all(promises) : Promise.resolve(); } - - /** - * Mark application as detected, set confidence and version - */ - addDetected(app, pattern, type, value, key) { - app.detected = true; - - // Set confidence level - app.confidence[type + ' ' + ( key ? key + ' ' : '' ) + pattern.regex] = pattern.confidence === undefined ? 100 : parseInt(pattern.confidence); - - // Detect version number - if ( pattern.version ) { - var versions = []; - var version = pattern.version; - var matches = pattern.regex.exec(value); - - if ( matches ) { - matches.forEach((match, i) => { - // Parse ternary operator - var ternary = new RegExp('\\\\' + i + '\\?([^:]+):(.*)$').exec(version); - - if ( ternary && ternary.length === 3 ) { - version = version.replace(ternary[0], match ? ternary[1] : ternary[2]); - } - - // Replace back references - version = version.trim().replace(new RegExp('\\\\' + i, 'g'), match || ''); - }); - - if ( version && versions.indexOf(version) === -1 ) { - versions.push(version); - } - - if ( versions.length ) { - // Use the longest detected version number - app.version = versions.reduce((a, b) => a.length > b.length ? a : b); - } - } - } - } -} - -/** - * Application class - */ -class Application { - constructor(name, props, detected) { - this.confidence = {}; - this.confidenceTotal = 0; - this.detected = Boolean(detected); - this.excludes = []; - this.name = name; - this.props = props; - this.version = ''; - } - - /** - * Calculate confidence total - */ - getConfidence() { - var total = 0; - - for ( let id in this.confidence ) { - total += this.confidence[id]; - } - - return this.confidenceTotal = Math.min(total, 100); - } } -if ( typeof module === 'object' ) { +if (typeof module === 'object') { module.exports = Wappalyzer; }