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/.gitignore b/.gitignore index 9e926bfbc..4c54d94c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /npm-debug.log /npm-debug.log +package-lock.json + !.gitkeep # Junk files @@ -13,3 +15,5 @@ Desktop.ini ._* tags tags.* +.idea +/nbproject/private/ diff --git a/.travis.yml b/.travis.yml index 2b27a8fa7..b39d23df3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ sudo: required services: - docker +before_install: npm install -g npm@latest +install: npm ci + after_success: - sha256sum build/* > build/SHA256SUMS - cat build/SHA256SUMS diff --git a/Dockerfile b/Dockerfile index f2a28e638..e7c9e6806 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine -MAINTAINER Elbert Alias +LABEL maintainer="elbert@alias.io" ENV WAPPALYZER_DIR=/opt/wappalyzer @@ -8,7 +8,6 @@ RUN apk update && apk add --no-cache \ bash \ curl \ fontconfig \ - nodejs \ nodejs-npm \ optipng \ zip @@ -17,14 +16,15 @@ RUN apk update && apk add --no-cache \ # https://github.com/dustinblackman/phantomized RUN curl -Ls "https://github.com/dustinblackman/phantomized/releases/download/2.1.1a/dockerized-phantomjs.tar.gz" | tar xz -C / -RUN apk del \ - curl +RUN apk del curl -RUN npm i -g \ +RUN npm i -g n npm@latest + +RUN n stable + +RUN npm i --unsafe-perm --silent -g \ jsonlint-cli \ - manifoldjs \ - svg2png-many \ - yarn + svg2png-many RUN mkdir -p $WAPPALYZER_DIR diff --git a/README.md b/README.md index 79b36246e..29e837b4b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Wappalyzer [![Travis](https://img.shields.io/travis/AliasIO/Wappalyzer.svg)](https://travis-ci.org/AliasIO/Wappalyzer/) [![Scrutinizer](https://scrutinizer-ci.com/g/AliasIO/Wappalyzer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/AliasIO/Wappalyzer/?branch=master) +# Wappalyzer [![Travis](https://travis-ci.org/AliasIO/Wappalyzer.svg?branch=master)](https://travis-ci.org/AliasIO/Wappalyzer/) [![Scrutinizer](https://scrutinizer-ci.com/g/AliasIO/Wappalyzer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/AliasIO/Wappalyzer/?branch=master) [Wappalyzer](https://www.wappalyzer.com/) is a [cross-platform](https://github.com/AliasIO/Wappalyzer/wiki/Drivers) utility that uncovers the diff --git a/bin/build b/bin/build index 2bdd89fa1..e39752ccc 100755 --- a/bin/build +++ b/bin/build @@ -22,7 +22,7 @@ find . -name ".DS_Store" -exec rm {} \; echo "Prettifying apps.json..." -jsonlint-cli -ist $'\t' src/apps.json +sed -e 's/\\\\/{{ESCAPE}}/g' src/apps.json | jsonlint-cli -ps | sed -e 's/{{ESCAPE}}/\\\\/g' > /tmp/apps.json && cat /tmp/apps.json > src/apps.json echo "Converting SVG icons to PNG..." @@ -60,6 +60,7 @@ zip -qr ../../../build/wappalyzer_webextension.zip . \ -x \*.gitkeep \ -x \*.js.map \ -x \*.min.js \ + -x \*.spec.js \ -x \*.yarn-integrity \ -x \*package.json \ -x \*LICENSE \ diff --git a/bin/validate b/bin/validate index dd9b12729..2c60e5b90 100755 --- a/bin/validate +++ b/bin/validate @@ -6,7 +6,7 @@ set -eu echo "Validating apps.json..." -jsonlint-cli -tps schema.json src/apps.json > /tmp/apps.json && mv /tmp/apps.json src/apps.json +jsonlint-cli -s schema.json src/apps.json echo "Validating regular expressions..." @@ -15,3 +15,7 @@ echo "Validating regular expressions..." echo "Validating icons..." ./bin/validate-icons + +echo "Running tests..." + +npm run test diff --git a/bin/validate-icons b/bin/validate-icons index 8a6913a46..e64efac02 100755 --- a/bin/validate-icons +++ b/bin/validate-icons @@ -16,7 +16,7 @@ var for (app in json.apps) { (function(app) { var - iconPath = json.apps[app].icon || 'default.svg'; + iconPath = json.apps[app].icon || 'default.svg', path = basePath + iconPath, ext = iconPath.substr(iconPath.length - 4); diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 000000000..4e55ba0c4 --- /dev/null +++ b/issue_template.md @@ -0,0 +1,16 @@ + + +**Do you want to request a *feature*, a *new application detection* or report a *bug*?** + +**Is your issue about WebExtension driver (Chrome & Firefox), the website, the NPM driver or the bookmarklet ?** + +**What is the current behavior ?** + +**If the current behavior is a bug, please provide the steps to reproduce and if possible a demo of the problem.** + +**What is the expected behavior ?** + +**Which versions of Wappalyzer, and which browser / OS are affected by this issue ? Did this work in previous versions ?** diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 000000000..221001f31 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,1781 @@ +{ + "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", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "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", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "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", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "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", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "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", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "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", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "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": { + "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" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "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": { + "builtin-modules": "^1.0.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, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "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", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "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", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "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", + "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", + "requires": { + "pify": "^3.0.0", + "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", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "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 2148f0f67..e7bee5930 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,19 @@ { "dependencies": { - "file-type": "3.8.*", - "is-svg": "2.0.*", - "read-chunk": "2.0.*" + "file-type": "7.4.*", + "is-svg": "2.1.*", + "read-chunk": "2.1.*" + }, + "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 src", + "lint": "eslint src", + "lint:fix": "eslint src --fix" } } - diff --git a/patches/zombie-vm-timeout.patch b/patches/zombie-vm-timeout.patch new file mode 100644 index 000000000..dcc62d7d4 --- /dev/null +++ b/patches/zombie-vm-timeout.patch @@ -0,0 +1,11 @@ +--- src/drivers/npm/node_modules/zombie/lib/document.js 2018-04-20 14:36:04.097829977 +1000 ++++ src/drivers/npm/node_modules/zombie/lib/document.js 2018-04-20 14:34:25.699317000 +1000 +@@ -281,7 +281,7 @@ + // The current window, postMessage and window.close need this + browser._windowInScope = window; + var result = undefined; +- if (typeof code === 'string' || code instanceof String) result = VM.runInContext(code, window, { filename: filename });else if (code) result = code.call(window); ++ if (typeof code === 'string' || code instanceof String) result = VM.runInContext(code, window, { filename: filename, timeout: 1000 });else if (code) result = code.call(window); + browser.emit('evaluated', code, result, filename); + return result; + } catch (error) { diff --git a/run b/run index 102fc812d..06556bb07 100755 --- a/run +++ b/run @@ -10,7 +10,17 @@ fi cmd="docker run --rm -v "$(pwd):/opt/wappalyzer" -it wappalyzer/dev" -$cmd sh -c "yarn install; cd src/drivers/webextension; yarn install" +$cmd sh -c "\ + npm i; \ + npm shrinkwrap; \ + cd src/drivers/webextension; \ + npm i; \ + npm shrinkwrap; \ + cd ../npm; \ + npm i; \ + npm shrinkwrap" + +$cmd sh -c "cat patches/*.patch | patch -p0" $cmd ./bin/run links $cmd ./bin/run $@ diff --git a/schema.json b/schema.json index 0f161fbeb..1d4ddb67d 100644 --- a/schema.json +++ b/schema.json @@ -3,6 +3,9 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { + "type": "string" + }, "categories": { "type": "object", "required": true, @@ -13,7 +16,7 @@ "required": true, "properties": { "priority": { - "type": "string" + "type": "number" }, "name": { "type": "string" @@ -31,10 +34,16 @@ "cats": { "type": "array", "items": { - "type": "string" + "type": "number" }, "required": true }, + "cookies": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "js": { "type": "object", "additionalProperties": { @@ -48,19 +57,28 @@ } }, "html": { - "type": [ "string", "array" ], + "type": [ + "string", + "array" + ], "items": { "type": "string" } }, "excludes": { - "type": [ "string", "array" ], + "type": [ + "string", + "array" + ], "items": { "type": "string" } }, "implies": { - "type": [ "string", "array" ], + "type": [ + "string", + "array" + ], "items": { "type": "string" } @@ -72,7 +90,10 @@ } }, "script": { - "type": [ "string", "array" ], + "type": [ + "string", + "array" + ], "items": { "type": "string" } diff --git a/src/apps.json b/src/apps.json index 23350f68f..8dd80a215 100644 --- a/src/apps.json +++ b/src/apps.json @@ -1,32 +1,13 @@ { + "$schema": "../schema.json", "apps": { - "1&1": { - "cats": [ - "6" - ], - "icon": "1and1.svg", - "implies": "PHP", - "url": "/shop/catalog/browse\\?sessid=", - "website": "http://1and1.com" - }, - "Google Analytics Enhanced eCommerce": { - "cats": [ - "10" - ], - "js": { - "gaplugins.EC": "" - }, - "icon": "Google Analytics.svg", - "script": "google-analytics\\.com\\/plugins\\/ua\\/ec\\.js", - "website": "https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce" - }, "1C-Bitrix": { "cats": [ - "1" + 1 ], "headers": { - "Set-Cookie": "^BITRIX_", - "X-Powered-CMS": "^Bitrix Site Manager" + "Set-Cookie": "BITRIX_", + "X-Powered-CMS": "Bitrix Site Manager" }, "html": "(?:]+components/bitrix|(?:src|href)=\"/bitrix/(?:js|templates))", "icon": "1C-Bitrix.png", @@ -34,60 +15,45 @@ "script": "1c-bitrix", "website": "http://www.1c-bitrix.ru" }, - "animate.css": { - "cats": [ - "18" - ], - "html": [ - "]+(?:/([\\d.]+)/)?animate\\.(?:min\\.)?css\\;version:\\1" - ], - "website": "https://daneden.github.io/animate.css/" - }, - "2z Project": { - "cats": [ - "1" - ], - "icon": "2z Project.png", - "meta": { - "generator": "2z project ([\\d.]+)\\;version:\\1" - }, - "website": "http://2zproject-cms.ru" - }, - "3DM": { + "91App": { "cats": [ - "19" + 6 ], - "html": "3ware 3DM([\\d\\.]+)?\\;version:\\1", - "icon": "3DM.png", - "implies": "3ware", - "website": "http://www.3ware.com" + "icon": "91app.png", + "script": "https\\:\\/\\/track\\.91app\\.io\\/track\\.js\\?", + "website": "https://www.91app.com/" }, "3dCart": { "cats": [ - "1", - "6" + 1, + 6 ], + "cookies": { + "3dvisit": "" + }, "headers": { - "Set-Cookie": "3dvisit", "X-Powered-By": "3DCART" }, "icon": "3dCart.png", "script": "(?:twlh(?:track)?\\.asp|3d_upsell\\.js)", "website": "http://www.3dcart.com" }, - "3ware": { + "A-Frame": { "cats": [ - "22" + 25 ], - "headers": { - "Server": "3ware\\/?([\\d\\.]+)?\\;version:\\1" + "html": "<a-scene[^<>]*>", + "icon": "A-Frame.svg", + "implies": "three.js", + "js": { + "AFRAME.version": "^(.+)$\\;version:\\1" }, - "icon": "3ware.png", - "website": "http://www.3ware.com" + "script": "/?([\\d.]+)?/aframe(?:\\.min)?\\.js\\;version:\\1", + "website": "https://aframe.io" }, "AD EBiS": { "cats": [ - "10" + 10 ], "html": [ "<!-- EBiS contents tag", @@ -98,35 +64,9 @@ "icon": "ebis.png", "website": "http://www.ebis.ne.jp" }, - "Amber": { - "cats": [ - "18", - "22" - ], - "headers": { - "X-Powered-By": "^Amber$" - }, - "icon": "amber.png", - "website": "https://amberframework.org" - }, - "AMPcms": { - "cats": [ - "1" - ], - "js": { - "amp_js_init": "" - }, - "headers": { - "Set-Cookie": "^AMP=", - "X-AMP-Version": "([\\d.]+)\\;version:\\1" - }, - "icon": "AMPcms.png", - "implies": "PHP", - "website": "http://www.ampcms.org" - }, "AOLserver": { "cats": [ - "22" + 22 ], "headers": { "Server": "AOLserver/?([\\d.]+)?\\;version:\\1" @@ -136,39 +76,29 @@ }, "AT Internet Analyzer": { "cats": [ - "10" + 10 ], + "icon": "AT Internet.png", "js": { - "xtsite": "", - "ATInternet": "" + "ATInternet": "", + "xtsite": "" }, - "icon": "AT Internet.png", "website": "http://atinternet.com/en" }, "AT Internet XiTi": { "cats": [ - "10" + 10 ], + "icon": "AT Internet.png", "js": { "xt_click": "" }, - "icon": "AT Internet.png", "script": "xiti\\.com/hit\\.xiti", "website": "http://atinternet.com/en" }, - "ATEN": { - "cats": [ - "22" - ], - "headers": { - "Server": "ATEN HTTP Server(?:\\(?V?([\\d\\.]+)\\)?)?\\;version:\\1" - }, - "icon": "ATEN.png", - "website": "http://www.aten.com" - }, "AWStats": { "cats": [ - "10" + 10 ], "icon": "AWStats.png", "implies": "Perl", @@ -177,45 +107,38 @@ }, "website": "http://awstats.sourceforge.net" }, - "Accessible Portal": { + "Accelerated Mobile Pages": { "cats": [ - "1" + 12 ], - "icon": "Accessible Portal.png", - "implies": "PHP", - "meta": { - "generator": "Accessible Portal" - }, - "website": "http://www.accessibleportal.com" + "html": "<html[^>]* (?:amp|⚡)", + "icon": "Accelerated-Mobile-Pages.svg", + "website": "https://www.ampproject.org" }, - "Act-On": { + "Acquia Cloud": { "cats": [ - "32" + 22 ], - "js": { - "ActOn": "" + "headers": { + "X-AH-Environment": "^\\w+$" }, - "icon": "ActOn.png", - "website": "http://act-on.com" + "icon": "acquia-cloud.png", + "implies": "Drupal\\;confidence:95", + "website": "https://www.acquia.com/" }, - "Prebid": { + "Act-On": { "cats": [ - "36" + 32 ], - "icon": "Prebid.png", + "icon": "ActOn.png", "js": { - "pbjs": "", - "PREBID_TIMEOUT": "" + "ActOn": "" }, - "script": [ - "/prebid\\.js", - "adnxs\\.com/[^\"]*(?:prebid|/pb\\.js)" - ], - "website": "http://prebid.org" + "website": "http://act-on.com" }, "AdInfinity": { "cats": [ - "36" + 36 ], "icon": "AdInfinity.png", "script": "adinfinity\\.com\\.au", @@ -223,49 +146,49 @@ }, "AdRiver": { "cats": [ - "36" + 36 ], + "html": "(?:<embed[^>]+(?:src=\"https?://mh\\d?\\.adriver\\.ru/|flashvars=\"[^\"]*(?:http:%3A//(?:ad|mh\\d?)\\.adriver\\.ru/|adriver_banner))|<(?:(?:iframe|img)[^>]+src|a[^>]+href)=\"https?://ad\\.adriver\\.ru/)", + "icon": "AdRiver.png", "js": { "adriver": "" }, - "html": "(?:<embed[^>]+(?:src=\"https?://mh\\d?\\.adriver\\.ru/|flashvars=\"[^\"]*(?:http:%3A//(?:ad|mh\\d?)\\.adriver\\.ru/|adriver_banner))|<(?:(?:iframe|img)[^>]+src|a[^>]+href)=\"https?://ad\\.adriver\\.ru/)", - "icon": "AdRiver.png", "script": "(?:adriver\\.core\\.\\d\\.js|https?://(?:content|ad|masterh\\d)\\.adriver\\.ru/)", "website": "http://adriver.ru" }, "AdRoll": { "cats": [ - "36" + 36 ], + "icon": "AdRoll.svg", "js": { "adroll_adv_id": "", "adroll_pix_id": "" }, - "icon": "AdRoll.svg", "script": "(?:a|s)\\.adroll\\.com", "website": "http://adroll.com" }, "Adcash": { "cats": [ - "36" + 36 ], + "icon": "Adcash.svg", "js": { + "SuLoaded": "", + "SuUrl": "", "ac_bgclick_URL": "", - "ct_siteunder": "", - "ct_tag": "", - "ct_nSuUrl": "", "ct_nOpp": "", - "SuLoaded": "", - "SuUrl": "" + "ct_nSuUrl": "", + "ct_siteunder": "", + "ct_tag": "" }, - "icon": "Adcash.svg", "script": "^[^\\/]*//(?:[^\\/]+\\.)?adcash\\.com/(?:script|ad)/", "url": "^https?://(?:[^\\/]+\\.)?adcash\\.com/script/pop_", "website": "http://adcash.com" }, "AddShoppers": { "cats": [ - "5" + 5 ], "icon": "AddShoppers.png", "script": "cdn\\.shop\\.pe/widget/", @@ -273,29 +196,29 @@ }, "AddThis": { "cats": [ - "5" + 5 ], + "icon": "AddThis.svg", "js": { "addthis": "" }, - "icon": "AddThis.svg", "script": "addthis\\.com/js/", "website": "http://www.addthis.com" }, "AddToAny": { "cats": [ - "5" + 5 ], + "icon": "AddToAny.png", "js": { "a2apage_init": "" }, - "icon": "AddToAny.png", "script": "addtoany\\.com/menu/page\\.js", "website": "http://www.addtoany.com" }, "Adminer": { "cats": [ - "3" + 3 ], "html": [ "Adminer</a> <span class=\"version\">([\\d.]+)</span>\\;version:\\1", @@ -307,7 +230,7 @@ }, "Adnegah": { "cats": [ - "36" + 36 ], "headers": { "X-Advertising-By": "adnegah\\.net" @@ -319,24 +242,32 @@ }, "Adobe ColdFusion": { "cats": [ - "18" + 18 ], - "js": { - "_cfEmails": "" - }, "headers": { "Cookie": "CFTOKEN=" }, "html": "<!-- START headerTags\\.cfm", "icon": "Adobe ColdFusion.svg", "implies": "CFML", + "js": { + "_cfEmails": "" + }, "script": "/cfajax/", "url": "\\.cfm(?:$|\\?)", "website": "http://adobe.com/products/coldfusion-family.html" }, + "Adobe DTM": { + "cats": [ + 42 + ], + "script": "//assets.adobedtm.com/", + "icon": "adobedmt.png", + "website": "https://marketing.adobe.com/resources/help/en_US/dtm/c_overview.html" + }, "Adobe Experience Manager": { "cats": [ - "1" + 1 ], "html": [ "<div class=\"[^\"]*parbase", @@ -349,7 +280,7 @@ }, "Adobe GoLive": { "cats": [ - "20" + 20 ], "icon": "Adobe GoLive.png", "meta": { @@ -359,7 +290,7 @@ }, "Adobe Muse": { "cats": [ - "20" + 20 ], "icon": "Adobe Muse.svg", "meta": { @@ -369,25 +300,36 @@ }, "Adobe RoboHelp": { "cats": [ - "4" + 4 ], + "icon": "Adobe RoboHelp.svg", "js": { - "gbWhVer": "", "gbWhLang": "", "gbWhMsg": "", + "gbWhProxy": "", "gbWhUtil": "", - "gbWhProxy": "" + "gbWhVer": "" }, - "icon": "Adobe RoboHelp.svg", "meta": { "generator": "^Adobe RoboHelp(?: ([\\d]+))?\\;version:\\1" }, "script": "(?:wh(?:utils|ver|proxy|lang|topic|msg)|ehlpdhtm)\\.js", "website": "http://adobe.com/products/robohelp.html" }, + "ADPLAN": { + "cats": [ + 10 + ], + "icon": "ADPLAN.png", + "script": [ + "^https?://[^.]+\\.adplan7\\.com/\\;version:7", + "^https?://(?!o\\.)\\w+\\.advg\\.jp/" + ], + "website": "https://www.adplan7.com/" + }, "Advanced Web Stats": { "cats": [ - "10" + 10 ], "html": "aws\\.src = [^<]+caphyon-analytics", "icon": "Advanced Web Stats.png", @@ -396,31 +338,41 @@ }, "Advert Stream": { "cats": [ - "36" + 36 ], + "icon": "Advert Stream.png", "js": { "advst_is_above_the_fold": "" }, - "icon": "Advert Stream.png", "script": "(?:ad\\.advertstream\\.com|adxcore\\.com)", "website": "http://www.advertstream.com" }, + "Adyen": { + "cats": [ + 41 + ], + "icon": "Adyen.svg", + "js": { + "adyen.encrypt.version": "^(.+)$\\;version:\\1" + }, + "website": "https://www.adyen.com" + }, "Adzerk": { "cats": [ - "36" + 36 ], + "html": "<iframe [^>]*src=\"[^\"]+adzerk\\.net", + "icon": "Adzerk.png", "js": { "ados": "", "adosResults": "" }, - "html": "<iframe [^>]*src=\"[^\"]+adzerk\\.net", - "icon": "Adzerk.png", "script": "adzerk\\.net/ados\\.js", "website": "http://adzerk.com" }, "Aegea": { "cats": [ - "11" + 11 ], "headers": { "X-Powered-By": "^E2 Aegea v(\\d+)$\\;version:\\1" @@ -432,9 +384,19 @@ ], "website": "http://blogengine.ru" }, + "Afosto": { + "cats": [ + 6 + ], + "headers": { + "X-Powered-By": "Afosto SaaS BV" + }, + "icon": "Afosto.svg", + "website": "http://afosto.com" + }, "AfterBuy": { "cats": [ - "6" + 6 ], "html": [ "<dd>This OnlineStore is brought to you by ViA-Online GmbH Afterbuy\\. Information and contribution at https://www\\.afterbuy\\.de</dd>" @@ -443,19 +405,23 @@ "script": "shop-static\\.afterbuy\\.de", "website": "http://www.afterbuy.de" }, - "Afosto": { + "Ahoy": { "cats": [ - "6" + 10 ], - "headers": { - "X-Powered-By": "Afosto SaaS BV" + "js": { + "ahoy": "" }, - "icon": "Afosto.svg", - "website": "http://afosto.com" + "cookies": { + "ahoy_track": "", + "ahoy_visit": "", + "ahoy_visitor": "" + }, + "website": "https://github.com/ankane/ahoy" }, "Aircall": { "cats": [ - "52" + 52 ], "icon": "aircall.png", "script": "^https?://cdn\\.aircall\\.io/", @@ -463,17 +429,17 @@ }, "Airee": { "cats": [ - "31" + 31 ], "headers": { - "Server": "Airee" + "Server": "^Airee" }, "icon": "Airee.png", "website": "http://xn--80aqc2a.xn--p1ai" }, "Akamai": { "cats": [ - "31" + 31 ], "headers": { "X-Akamai-Transformed": "" @@ -481,10 +447,25 @@ "icon": "akamai.svg", "website": "http://akamai.com" }, + "Akaunting": { + "cats": [ + 55 + ], + "headers": { + "X-Akaunting": "^Free Accounting Software$" + }, + "html": [ + "<link[^>]+akaunting-green\\.css", + "Powered By Akaunting: <a [^>]*href=\"https?://(?:www\\.)?akaunting\\.com[^>]+>" + ], + "icon": "akaunting.svg", + "implies": "Laravel", + "website": "https://akaunting.com" + }, "Akka HTTP": { "cats": [ - "18", - "22" + 18, + 22 ], "headers": { "Server": "akka-http(?:/([\\d.]+))?\\;version:\\1" @@ -494,17 +475,27 @@ }, "Algolia Realtime Search": { "cats": [ - "29" + 29 ], + "icon": "Algolia Realtime Search.svg", "js": { - "AlgoliaSearch": "" + "AlgoliaSearch": "", + "algoliasearch.version": "^(.+)$\\;version:\\1" }, - "icon": "Algolia Realtime Search.svg", "website": "http://www.algolia.com" }, + "All in One SEO Pack": { + "cats": [ + 54 + ], + "html": "<!-- All in One SEO Pack ([\\d.]+) \\;version:\\1", + "icon": "all-in-One-SEO-Pack.png", + "implies": "WordPress", + "website": "https://www.acquia.com/" + }, "Allegro RomPager": { "cats": [ - "22" + 22 ], "headers": { "Server": "Allegro-Software-RomPager(?:/([\\d.]+))?\\;version:\\1" @@ -514,22 +505,22 @@ }, "AlloyUI": { "cats": [ - "12" + 12 ], - "js": { - "AUI": "" - }, "icon": "AlloyUI.png", "implies": [ "Bootstrap", "YUI" ], + "js": { + "AUI": "" + }, "script": "^https?://cdn\\.alloyui\\.com/", "website": "http://www.alloyui.com" }, "Amaya": { "cats": [ - "20" + 20 ], "icon": "Amaya.png", "meta": { @@ -539,37 +530,84 @@ }, "Amazon Cloudfront": { "cats": [ - "31" + 31 ], "headers": { + "Via": "\\(CloudFront\\)$", "X-Amz-Cf-Id": "" }, "icon": "Amazon-Cloudfront.svg", + "implies": "Amazon Web Services", "website": "http://aws.amazon.com/cloudfront/" }, "Amazon EC2": { "cats": [ - "22" + 22 ], "headers": { "Server": "\\(Amazon\\)" }, "icon": "aws-ec2.svg", + "implies": "Amazon Web Services", "website": "http://aws.amazon.com/ec2/" }, + "Amazon Web Services": { + "cats": [ + 62 + ], + "icon": "aws.svg", + "website": "https://aws.amazon.com/" + }, + "Amazon ECS": { + "cats": [ + 63 + ], + "headers": { + "Server": "^ECS" + }, + "icon": "aws.svg", + "implies": [ + "Amazon Web Services", + "Docker" + ], + "website": "https://aws.amazon.com/elasticloadbalancing/" + }, + "Amazon ELB": { + "cats": [ + 63 + ], + "cookies": { + "AWSELB": "" + }, + "icon": "aws-elb.png", + "implies": "Amazon Web Services", + "website": "https://aws.amazon.com/elasticloadbalancing/" + }, "Amazon S3": { "cats": [ - "19" + 19 ], "headers": { - "Server": "AmazonS3" + "Server": "^AmazonS3$" }, "icon": "aws-s3.svg", + "implies": "Amazon Web Services", "website": "http://aws.amazon.com/s3/" }, + "Amber": { + "cats": [ + 18, + 22 + ], + "headers": { + "X-Powered-By": "^Amber$" + }, + "icon": "amber.png", + "website": "https://amberframework.org" + }, "Ametys": { "cats": [ - "1" + 1 ], "icon": "Ametys.png", "implies": "Java", @@ -581,7 +619,7 @@ }, "Amiro.CMS": { "cats": [ - "1" + 1 ], "icon": "Amiro.CMS.png", "implies": "PHP", @@ -590,51 +628,121 @@ }, "website": "http://amirocms.com" }, + "Amplitude": { + "cats": [ + 10 + ], + "icon": "amplitude.png", + "script": [ + "cdn\\.amplitude\\.com" + ], + "website": "https://amplitude.com/" + }, + "Anetwork": { + "cats": [ + 36 + ], + "icon": "Anetwork.png", + "script": "static-cdn\\.anetwork\\.ir/", + "website": "https://www.anetwork.ir" + }, + "Angular": { + "cats": [ + 12 + ], + "excludes": [ + "AngularDart", + "AngularJS" + ], + "html": "<[^>]+ ng-version=\"([\\d.]+)\"\\;version:\\1", + "icon": "Angular.svg", + "js": { + "ng.coreTokens": "", + "ng.probe": "" + }, + "website": "https://angular.io" + }, "Angular Material": { "cats": [ - "18" + 18 ], + "icon": "AngularJS.svg", + "implies": "AngularJS", "js": { "ngMaterial": "" }, - "icon": "Angular.svg", - "implies": "AngularJS", - "script": [ - "/([\\d.]+(?:\\-?rc[.\\d]*)*)/angular-material(?:\\.min)?\\.js\\;version:\\1", - "angular-material.*\\.js" + "script": "/([\\d.rc-]+)?/angular-material(?:\\.min)?\\.js\\;version:\\1", + "website": "https://material.angularjs.org" + }, + "AngularDart": { + "cats": [ + 18 ], - "website": "http://material.angularjs.org" + "excludes": [ + "Angular", + "AngularJS" + ], + "icon": "AngularDart.svg", + "implies": "Dart", + "js": { + "ngTestabilityRegistries": "" + }, + "website": "https://webdev.dartlang.org/angular/" }, - "Angular": { + "AngularJS": { "cats": [ - "12" + 12 + ], + "excludes": [ + "Angular", + "AngularDart" + ], + "icon": "AngularJS.svg", + "html": [ + "<(?:div|html)[^>]+ng-app=", + "<ng-app" ], "js": { "angular": "", - "angular.version.full": "(.*)\\;version:\\1" + "angular.version.full": "^(.+)$\\;version:\\1" }, - "html": "<app[^>]+ng-version=\"([\\d.]+)\">\\;version:\\1", - "icon": "Angular.svg", "script": [ - "angular(?:\\-|\\.)([\\d.]*\\d)[^/]*\\.js\\;version:\\1", - "/([\\d.]+(?:\\-?rc[.\\d]*)*)/angular(?:\\.min)?\\.js\\;version:\\1", + "angular[.-]([\\d.]*\\d)[^/]*\\.js\\;version:\\1", + "/([\\d.]+(?:-?rc[.\\d]*)*)/angular(?:\\.min)?\\.js\\;version:\\1", "angular.*\\.js" ], - "website": "http://angular.io" + "website": "https://angularjs.org" + }, + "Ant Design": { + "cats": [ + 12 + ], + "html": [ + "<[^>]*class=\"ant-(?:btn|col|row|layout|breadcrumb|menu|pagination|steps|select|cascader|checkbox|calendar|form|input-number|input|mention|rate|radio|slider|switch|tree-select|time-picker|transfer|upload|avatar|badge|card|carousel|collapse|list|popover|tooltip|table|tabs|tag|timeline|tree|alert|modal|message|notification|progress|popconfirm|spin|anchor|back-top|divider|drawer)", + "<i class=\"anticon anticon-" + ], + "icon": "Ant Design.svg", + "implies": [ + "React" + ], + "js": { + "antd": "" + }, + "website": "https://ant.design" }, "Apache": { "cats": [ - "22" + 22 ], "headers": { - "Server": "(?:Apache(?:$|/([\\d.]+)|[^/-])|(?:^|\b)HTTPD)\\;version:\\1" + "Server": "(?:Apache(?:$|/([\\d.]+)|[^/-])|(?:^|\\b)HTTPD)\\;version:\\1" }, "icon": "Apache.svg", "website": "http://apache.org" }, "Apache HBase": { "cats": [ - "34" + 34 ], "html": "<style[^>]+static/hbase", "icon": "Apache HBase.png", @@ -643,7 +751,7 @@ }, "Apache Hadoop": { "cats": [ - "34" + 34 ], "html": "<style[^>]+static/hadoop", "icon": "Apache Hadoop.svg", @@ -651,7 +759,7 @@ }, "Apache JSPWiki": { "cats": [ - "8" + 8 ], "html": "<html[^>]* xmlns:jspwiki=", "icon": "Apache JSPWiki.png", @@ -662,11 +770,11 @@ }, "Apache Tomcat": { "cats": [ - "22" + 22 ], "headers": { - "Server": "^Apache-Coyote(/1\\.1)?$\\;version:\\1?4.1+:", - "X-Powered-By": "\bTomcat\b(?:-([\\d.]+))?\\;version:\\1" + "Server": "^Apache-Coyote(?:/([\\d.]+))?\\;version:\\1", + "X-Powered-By": "\\bTomcat\\b(?:-([\\d.]+))?\\;version:\\1" }, "icon": "Apache Tomcat.svg", "implies": "Java", @@ -674,7 +782,7 @@ }, "Apache Traffic Server": { "cats": [ - "22" + 22 ], "headers": { "Server": "ATS/?([\\d.]+)?\\;version:\\1" @@ -684,18 +792,18 @@ }, "Apache Wicket": { "cats": [ - "18" + 18 ], + "icon": "Apache Wicket.svg", + "implies": "Java", "js": { "Wicket": "" }, - "icon": "Apache Wicket.svg", - "implies": "Java", "website": "http://wicket.apache.org" }, "ApexPages": { "cats": [ - "51" + 51 ], "headers": { "X-Powered-By": "Salesforce\\.com ApexPages" @@ -706,7 +814,7 @@ }, "Apostrophe CMS": { "cats": [ - "1" + 1 ], "html": "<[^>]+data-apos-refreshable[^>]", "icon": "apostrophecms.svg", @@ -715,20 +823,28 @@ }, "AppNexus": { "cats": [ - "36" + 36 ], "html": "<(?:iframe|img)[^>]+adnxs\\.(?:net|com)", "icon": "AppNexus.svg", "script": "adnxs\\.(?:net|com)", "website": "http://appnexus.com" }, + "Appcues": { + "cats": [ + 58 + ], + "icon": "Appcues.svg", + "script": "fast\\.appcues.com*\\.js", + "website": "https://appcues.com" + }, "Arastta": { "cats": [ - "6" + 6 ], "excludes": "OpenCart", "headers": { - "Arastta": "(.*)\\;version:\\1", + "Arastta": "^(.+)$\\;version:\\1", "X-Arastta": "" }, "html": "Powered by <a [^>]*href=\"https?://(?:www\\.)?arastta\\.org[^>]+>Arastta", @@ -737,44 +853,36 @@ "script": "arastta\\.js", "website": "http://arastta.org" }, - "Arc Forum": { - "cats": [ - "2" - ], - "html": "ping\\.src = node\\.href;\\s+[^>]+\\s+}\\s+</script>", - "icon": "Arc Forum.png", - "website": "http://arclanguage.org" - }, "ArcGIS API for JavaScript": { "cats": [ - "35" + 35 ], + "icon": "arcgis_icon.png", "script": [ "js\\.arcgis\\.com", "basemaps\\.arcgis\\.com" ], - "icon": "arcgis_icon.png", "website": "https://developers.arcgis.com/javascript/" }, "Artifactory": { "cats": [ - "47" + 47 ], - "js": { - "ArtifactoryUpdates": "" - }, "html": [ "<span class=\"version\">Artifactory(?: Pro)?(?: Power Pack)?(?: ([\\d.]+))?\\;version:\\1" ], "icon": "Artifactory.svg", - "script": [ + "js": { + "ArtifactoryUpdates": "" + }, + "script": [ "wicket/resource/org\\.artifactory\\." ], "website": "http://jfrog.com/open-source/#os-arti" }, "Artifactory Web Server": { "cats": [ - "22" + 22 ], "headers": { "Server": "Artifactory(?:/([\\d.]+))?\\;version:\\1" @@ -787,27 +895,27 @@ }, "ArvanCloud": { "cats": [ - "31" + 31 ], - "js": { - "ArvanCloud": "" - }, "headers": { "AR-PoweredBy": "Arvan Cloud \\(arvancloud\\.com\\)" }, "icon": "ArvanCloud.png", + "js": { + "ArvanCloud": "" + }, "website": "http://www.ArvanCloud.com" }, "AsciiDoc": { "cats": [ - "1", - "20", - "27" + 1, + 20, + 27 ], + "icon": "AsciiDoc.png", "js": { "asciidoc": "" }, - "icon": "AsciiDoc.png", "meta": { "generator": "^AsciiDoc ([\\d.]+)\\;version:\\1" }, @@ -815,26 +923,26 @@ }, "Asciinema": { "cats": [ - "14" + 14 ], + "html": "<asciinema-player", + "icon": "asciinema.png", "js": { "asciinema": "" }, "script": "asciinema\\.org/", - "icon": "asciinema.png", - "html": "<asciinema-player", "website": "https://asciinema.org/" }, "Atlassian Bitbucket": { "cats": [ - "47" + 47 ], + "html": "<li>Atlassian Bitbucket <span title=\"[a-z0-9]+\" id=\"product-version\" data-commitid=\"[a-z0-9]+\" data-system-build-number=\"[a-z0-9]+\"> v([\\d.]+)<\\;version:\\1", + "icon": "Atlassian Bitbucket.svg", + "implies": "Python", "js": { "bitbucket": "" }, - "icon": "Atlassian Bitbucket.svg", - "implies": "Python", - "html": "<li>Atlassian Bitbucket <span title=\"[a-z0-9]+\" id=\"product-version\" data-commitid=\"[a-z0-9]+\" data-system-build-number=\"[a-z0-9]+\"> v([\\d.]+)<\\;version:\\1", "meta": { "application-name": "Bitbucket" }, @@ -842,7 +950,7 @@ }, "Atlassian Confluence": { "cats": [ - "8" + 8 ], "headers": { "X-Confluence-Request-Time": "" @@ -857,10 +965,10 @@ }, "Atlassian FishEye": { "cats": [ - "47" + 47 ], - "headers": { - "Set-cookie": "FESESSIONID" + "cookies": { + "FESESSIONID": "" }, "html": "<title>(?:Log in to )?FishEye (?:and Crucible )?([\\d.]+)?\\;version:\\1", "icon": "Atlassian FishEye.svg", @@ -868,24 +976,24 @@ }, "Atlassian Jira": { "cats": [ - "13" + 13 ], - "js": { - "jira": "" - }, "html": "Powered by\\s+]+atlassian\\.com/(?:software/jira|jira-bug-tracking/)[^>]+>Atlassian\\s+JIRA(?:[^v]*v(?:ersion: )?(\\d+\\.\\d+(?:\\.\\d+)?))?\\;version:\\1", "icon": "Atlassian Jira.svg", "implies": "Java", + "js": { + "jira": "" + }, "meta": { - "ajs-version-number": "([\\d\\.]+)\\;version:\\1", + "ajs-version-number": "^(.+)$\\;version:\\1", "application-name": "JIRA" }, "website": "http://www.atlassian.com/software/jira/overview/" }, "Atlassian Jira Issue Collector": { "cats": [ - "13", - "47" + 13, + 47 ], "icon": "Atlassian Jira.svg", "script": [ @@ -896,7 +1004,7 @@ }, "Aurelia": { "cats": [ - "12" + 12 ], "html": [ "<[^>]+aurelia-app=[^>]", @@ -911,20 +1019,31 @@ }, "Avangate": { "cats": [ - "6" + 6 ], - "js": { - "avng8_": "", - "__avng8_": "" - }, "html": "]* href=\"^https?://edge\\.avangate\\.net/", "icon": "Avangate.svg", + "js": { + "__avng8_": "", + "avng8_": "" + }, "script": "^https?://edge\\.avangate\\.net/", "website": "http://avangate.com" }, + "Awesomplete": { + "cats": [ + 29 + ], + "html": "]+href=\"[^>]*awesomplete(?:\\.min)?\\.css", + "js": { + "awesomplete": "" + }, + "script": "/awesomplete\\.js(?:$|\\?)", + "website": "https://leaverou.github.io/awesomplete/" + }, "BEM": { "cats": [ - "12" + 12 ], "html": "<[^>]+data-bem", "icon": "BEM.png", @@ -932,7 +1051,7 @@ }, "BIGACE": { "cats": [ - "1" + 1 ], "html": "(?:Powered by ]+BIGACE|", "icon": "Business Catalyst.png", @@ -1282,50 +1464,53 @@ }, "BuySellAds": { "cats": [ - "36" + 36 ], + "script": "^https?://s\\d\\.buysellads\\.com/", + "icon": "BuySellAds.png", "js": { "_bsa": "", - "_bsap": "", "_bsaPRO": "", + "_bsap": "", "_bsap_serving_callback": "" }, - "html": "]*>[^<]+?bsa\\.src\\s*=\\s*['\"](?:https?:)?\\/{2}\\w\\d\\.buysellads\\.com\\/[\\w\\d\\/]+?bsa\\.js['\"]", - "icon": "BuySellAds.png", - "script": "^https?://s\\d\\.buysellads\\.com/", "website": "http://buysellads.com" }, - "C++": { + "CDN77": { "cats": [ - "27" + 31 ], - "icon": "C++.png", - "website": "http://isocpp.org" + "headers": { + "Server": "^CDN77-Turbo$" + }, + "icon": "CDN77.png", + "website": "https://www.cdn77.com" }, "CFML": { "cats": [ - "27" + 27 ], "icon": "CFML.png", "website": "http://adobe.com/products/coldfusion-family.html" }, "CKEditor": { "cats": [ - "24" + 24 ], + "icon": "CKEditor.png", "js": { "CKEDITOR": "", - "CKEDITOR.version": "(.*)\\;version:\\1" + "CKEDITOR.version": "^(.+)$\\;version:\\1", + "CKEDITOR_BASEPATH": "" }, - "icon": "CKEditor.png", "website": "http://ckeditor.com" }, "CMS Made Simple": { "cats": [ - "1" + 1 ], - "headers": { - "Set-Cookie": "^CMSSESSID" + "cookies": { + "CMSSESSID": "" }, "icon": "CMS Made Simple.png", "implies": "PHP", @@ -1336,7 +1521,7 @@ }, "CMSimple": { "cats": [ - "1" + 1 ], "implies": "PHP", "meta": { @@ -1344,17 +1529,9 @@ }, "website": "http://www.cmsimple.org/en" }, - "CO2Stats": { - "cats": [ - "10" - ], - "html": "src=[^>]+co2stats\\.com/propres\\.php", - "icon": "CO2Stats.png", - "website": "http://co2stats.com" - }, "CPG Dragonfly": { "cats": [ - "1" + 1 ], "headers": { "X-Powered-By": "^Dragonfly CMS" @@ -1368,22 +1545,22 @@ }, "CS Cart": { "cats": [ - "6" + 6 ], - "js": { - "fn_compare_strings": "" - }, "html": [ " Powered by (?:]+cs-cart\\.com|CS-Cart)", "\\.cm-noscript[^>]+" ], "icon": "CS Cart.png", "implies": "PHP", + "js": { + "fn_compare_strings": "" + }, "website": "http://www.cs-cart.com" }, "CacheFly": { "cats": [ - "31" + 31 ], "headers": { "Server": "^CFS ", @@ -1395,7 +1572,7 @@ }, "Caddy": { "cats": [ - "22" + 22 ], "headers": { "Server": "^Caddy$" @@ -1406,10 +1583,10 @@ }, "CakePHP": { "cats": [ - "18" + 18 ], - "headers": { - "Set-Cookie": "cakephp=" + "cookies": { + "cakephp": "" }, "icon": "CakePHP.png", "implies": "PHP", @@ -1418,51 +1595,33 @@ }, "website": "http://cakephp.org" }, - "Canon": { - "cats": [ - "40" - ], - "icon": "Canon.png", - "website": "http://www.canon.com" - }, - "Canon HTTP Server": { - "cats": [ - "22" - ], - "headers": { - "Server": "CANON HTTP Server(?:/([\\d.]+))?\\;version:\\1" - }, - "icon": "Canon.png", - "implies": "Canon", - "website": "http://www.canon.com" - }, "Captch Me": { "cats": [ - "16", - "36" + 16, + 36 ], + "icon": "Captch Me.svg", "js": { "Captchme": "" }, - "icon": "Captch Me.svg", "script": "^https?://api\\.captchme\\.net/", "website": "http://captchme.com" }, "Carbon Ads": { "cats": [ - "36" + 36 ], + "html": "<[a-z]+ [^>]*id=\"carbonads-container\"", + "icon": "Carbon Ads.png", "js": { "_carbonads": "" }, - "html": "<[a-z]+ [^>]*id=\"carbonads-container\"", - "icon": "Carbon Ads.png", - "script": "[^\\/]*\\/\\/(?:engine|srv)\\.carbonads\\.com\\/", + "script": "//(?:engine|srv)\\.carbonads\\.com\\/", "website": "http://carbonads.net" }, "Cargo": { "cats": [ - "1" + 1 ], "html": "]+Cargo feed", "icon": "Cargo.png", @@ -1475,34 +1634,23 @@ }, "Catberry.js": { "cats": [ - "12", - "18" + 12, + 18 ], - "js": { - "catberry": "", - "catberry.version": "(.*)\\;version:\\1" - }, "headers": { "X-Powered-By": "Catberry" }, "icon": "Catberry.js.png", "implies": "Node.js", - "website": "http://catberry.org" - }, - "Catwalk": { - "cats": [ - "22" - ], - "headers": { - "Server": "Catwalk\\/?([\\d\\.]+)?\\;version:\\1" + "js": { + "catberry": "", + "catberry.version": "^(.+)$\\;version:\\1" }, - "icon": "Catwalk.png", - "implies": "Canon", - "website": "http://www.canon.com" + "website": "http://catberry.org" }, "CentOS": { "cats": [ - "28" + 28 ], "headers": { "Server": "CentOS", @@ -1511,19 +1659,9 @@ "icon": "CentOS.png", "website": "http://centos.org" }, - "CenteHTTPd": { - "cats": [ - "22" - ], - "headers": { - "Server": "CenteHTTPd(?:/([\\d.]+))?\\;version:\\1" - }, - "icon": "CenteHTTPd.png", - "website": "http://cente.jp/cente/app/HTTPdc.html" - }, "Chameleon": { "cats": [ - "1" + 1 ], "icon": "Chameleon.png", "implies": [ @@ -1537,7 +1675,7 @@ }, "Chamilo": { "cats": [ - "21" + 21 ], "headers": { "X-Powered-By": "Chamilo ([\\d.]+)\\;version:\\1" @@ -1552,48 +1690,48 @@ }, "Chart.js": { "cats": [ - "25" + 25 ], + "icon": "Chart.js.svg", "js": { "Chart": "\\;confidence:50", + "Chart.defaults.doughnut": "", "chart.ctx.bezierCurveTo": "" }, - "icon": "Chart.js.svg", "script": [ - "Chart(?:\\.bundle)?(?:\\.min)?\\.js\\;confidence:50", + "/Chart(?:\\.bundle)?(?:\\.min)?\\.js\\;confidence:75", "chartjs\\.org/dist/([\\d.]+(?:-[^/]+)?|master|latest)/Chart.*\\.js\\;version:\\1", "cdnjs\\.cloudflare\\.com/ajax/libs/Chart\\.js/([\\d.]+(?:-[^/]+)?)/Chart.*\\.js\\;version:\\1", - "cdn\\.jsdelivr\\.net/npm/chart\\.js@([\\d.]+(?:-[^/]+)?|latest)/dist/Chart.*\\.js\\;version:\\1", - "cdn\\.jsdelivr\\.net/gh/chartjs/Chart\\.js@([\\d.]+(?:-[^/]+)?|latest)/dist/Chart.*\\.js\\;version:\\1" + "cdn\\.jsdelivr\\.net/(?:npm|gh/chartjs)/chart\\.js@([\\d.]+(?:-[^/]+)?|latest)/dist/Chart.*\\.js\\;version:\\1" ], - "website": "http://www.chartjs.org" + "website": "https://www.chartjs.org" }, "Chartbeat": { "cats": [ - "10" + 10 ], + "icon": "Chartbeat.png", "js": { - "_sf_endpt": "", - "_sf_async_config": "" + "_sf_async_config": "", + "_sf_endpt": "" }, - "icon": "Chartbeat.png", "script": "chartbeat\\.js", "website": "http://chartbeat.com" }, "Cherokee": { "cats": [ - "22" + 22 ], "headers": { - "Server": "Cherokee(?:/([\\d.]+))?\\;version:\\1" + "Server": "^Cherokee(?:/([\\d.]+))?\\;version:\\1" }, "icon": "Cherokee.png", "website": "http://www.cherokee-project.com" }, "CherryPy": { "cats": [ - "18", - "22" + 18, + 22 ], "headers": { "Server": "CherryPy\\/?([\\d\\.]+)?\\;version:\\1" @@ -1602,21 +1740,34 @@ "implies": "Python", "website": "http://www.cherrypy.org" }, + "Chevereto": { + "cats": [ + 7 + ], + "meta": { + "generator": "^Chevereto ?([0-9.]+)?$\\;version:\\1" + }, + "script": "/chevereto\\.js", + "html": "Powered by ", + "icon": "chevereto.png", + "implies": "PHP", + "website": "https://chevereto.com/" + }, "Chitika": { "cats": [ - "36" + 36 ], + "icon": "Chitika.png", "js": { "ch_client": "", "ch_color_site_link": "" }, - "icon": "Chitika.png", "script": "scripts\\.chitika\\.net/", "website": "http://chitika.com" }, "Ckan": { "cats": [ - "1" + 1 ], "headers": { "Access-Control-Allow-Headers": "X-CKAN-API-KEY", @@ -1630,46 +1781,46 @@ "PostgreSQL" ], "meta": { - "generator": ".*ckan.*" + "generator": "^ckan ?([0-9.]+)$\\;version:\\1" }, "website": "http://ckan.org/" }, "ClickHeat": { "cats": [ - "10" + 10 ], + "icon": "ClickHeat.png", + "implies": "PHP", "js": { "clickHeatServer": "" }, - "icon": "ClickHeat.png", - "implies": "PHP", "script": "clickheat.*\\.js", "website": "http://www.labsmedia.com/clickheat/index.html" }, "ClickTale": { "cats": [ - "10" + 10 ], + "icon": "ClickTale.png", "js": { "clickTaleStartEventSignal": "" }, - "icon": "ClickTale.png", "website": "http://www.clicktale.com" }, "Clicky": { "cats": [ - "10" + 10 ], + "icon": "Clicky.png", "js": { "clicky": "" }, - "icon": "Clicky.png", "script": "static\\.getclicky\\.com", "website": "http://getclicky.com" }, "Clientexec": { "cats": [ - "6" + 6 ], "html": "clientexec\\.[^>]*\\s?=\\s?[^>]*;", "icon": "Clientexec.png", @@ -1677,15 +1828,15 @@ }, "Clipboard.js": { "cats": [ - "19" + 19 ], "icon": "Clipboard.js.svg", - "script": "clipboard(?:\\.min)?\\.js", + "script": "clipboard(?:-([\\d.]+))?(?:\\.min)?\\.js\\;version:\\1", "website": "https://clipboardjs.com/" }, "CloudCart": { "cats": [ - "6" + 6 ], "icon": "cloudcart.svg", "meta": { @@ -1694,32 +1845,37 @@ "script": "/cloudcart-(?:assets|storage)/", "website": "http://cloudcart.com" }, - "Cloudcoins": { + "CloudFlare": { "cats": [ - "56" + 31 ], + "headers": { + "Server": "^cloudflare$", + "cf-cache-status": "", + "cf-ray": "" + }, + "icon": "CloudFlare.svg", + "cookies": { + "__cfduid": "" + }, "js": { - "CLOUDCOINS": "" + "CloudFlare": "" }, - "script": "https?://cdn\\.cloudcoins\\.co/javascript/cloudcoins\\.min\\.js", - "website": "https://cloudcoins.co" + "website": "http://www.cloudflare.com" }, - "CloudFlare": { + "Cloudcoins": { "cats": [ - "31" + 56 ], "js": { - "CloudFlare": "" - }, - "headers": { - "Server": "cloudflare" + "CLOUDCOINS": "" }, - "icon": "CloudFlare.svg", - "website": "http://www.cloudflare.com" + "script": "https?://cdn\\.cloudcoins\\.co/javascript/cloudcoins\\.min\\.js", + "website": "https://cloudcoins.co" }, "Cloudera": { "cats": [ - "34" + 34 ], "headers": { "Server": "cloudera" @@ -1727,12 +1883,26 @@ "icon": "Cloudera.png", "website": "http://www.cloudera.com" }, + "Coaster CMS": { + "cats": [ + 1 + ], + "icon": "coaster-cms.png", + "implies": "Laravel", + "meta": { + "generator": "^Coaster CMS v([\\d.]+)$\\;version:\\1" + }, + "website": "https://www.coastercms.org" + }, "CodeIgniter": { "cats": [ - "18" + 18 ], - "headers": { - "Set-Cookie": "(?:exp_last_activity|exp_tracker|ci_(?:session|(csrf_token)))\\;version:\\1?2+:" + "cookies": { + "ci_csrf_token": "^(.+)$\\;version:\\1?2+:", + "ci_session": "", + "exp_last_activity": "", + "exp_tracker": "" }, "html": "]+name=\"ci_csrf_token\"\\;version:2+", "icon": "CodeIgniter.png", @@ -1741,70 +1911,94 @@ }, "CodeMirror": { "cats": [ - "19" + 19 ], + "icon": "CodeMirror.png", "js": { "CodeMirror": "", - "CodeMirror.version": "(.*)\\;version:\\1" + "CodeMirror.version": "^(.+)$\\;version:\\1" }, - "icon": "CodeMirror.png", "website": "http://codemirror.net" }, "CoinHive": { "cats": [ - "56" + 56 ], + "icon": "CoinHive.svg", "js": { "CoinHive": "" }, - "script": "\\/(?:coinhive|(authedmine))(?:\\.min)?\\.js\\;version:\\1?opt-in:", + "script": [ + "\\/(?:coinhive|(authedmine))(?:\\.min)?\\.js\\;version:\\1?opt-in:", + "coinhive\\.com/lib" + ], "url": "https?://cnhv\\.co/", - "icon": "CoinHive.svg", "website": "https://coinhive.com" }, "CoinHive Captcha": { "cats": [ - "16", - "56" + 16, + 56 ], "html": "(?:]+class=\"coinhive-captcha[^>]+data-key|]+data-key[^>]+class=\"coinhive-captcha)", - "script": "https?://authedmine\\.com/(?:lib/captcha|captcha)", "icon": "CoinHive.svg", + "script": "https?://authedmine\\.com/(?:lib/captcha|captcha)", "website": "https://coinhive.com" }, + "Coinhave": { + "cats": [ + 56 + ], + "icon": "coinhave.png", + "script": "https?://coin-have\\.com/c/[0-9a-zA-Z]{4}\\.js", + "website": "https://coin-have.com/" + }, "Coinimp": { "cats": [ - "56" + 56 ], - "script": "https?://www\\.hashing\\.win/scripts/min\\.js", "icon": "coinimp.png", + "js": { + "Client.Anonymous": "\\;confidence:50" + }, + "script": "https?://www\\.hashing\\.win/scripts/min\\.js", "website": "https://www.coinimp.com" }, "Coinlab": { "cats": [ - "56" + 56 ], + "icon": "coinlab.png", "js": { "Coinlab": "" }, "script": "https?://coinlab\\.biz/lib/coinlab\\.js\\?id=", - "icon": "coinlab.png", "website": "https://coinlab.biz/en" }, - "Comandia": { + "ColorMeShop": { "cats": [ - "6" + 6 ], + "icon": "colormeshop.png", "js": { - "Comandia": "" + "Colorme": "" }, + "website": "https://shop-pro.jp" + }, + "Comandia": { + "cats": [ + 6 + ], "html": "]+=['\"]//cdn\\.mycomandia\\.com", "icon": "Comandia.svg", + "js": { + "Comandia": "" + }, "website": "http://comandia.com" }, "Commerce Server": { "cats": [ - "6" + 6 ], "headers": { "COMMERCE-SERVER-SOFTWARE": "" @@ -1815,7 +2009,7 @@ }, "CompaqHTTPServer": { "cats": [ - "22" + 22 ], "headers": { "Server": "CompaqHTTPServer\\/?([\\d\\.]+)?\\;version:\\1" @@ -1825,22 +2019,25 @@ }, "Concrete5": { "cats": [ - "1" + 1 ], + "icon": "Concrete5.png", + "implies": "PHP", "js": { "CCM_IMAGE_PATH": "" }, - "icon": "Concrete5.png", - "implies": "PHP", + "cookies": { + "CONCRETE5": "" + }, "meta": { - "generator": "concrete5 - ([\\d.ab]+)\\;version:\\1" + "generator": "^concrete5 - ([\\d.]+)$\\;version:\\1" }, - "script": "concrete/js/", - "website": "http://concrete5.org" + "script": "/concrete/js/", + "website": "https://concrete5.org" }, "Connect": { "cats": [ - "18" + 18 ], "headers": { "X-Powered-By": "^Connect$" @@ -1851,7 +2048,7 @@ }, "Contao": { "cats": [ - "1" + 1 ], "html": [ "", @@ -1866,7 +2063,7 @@ }, "Contenido": { "cats": [ - "1" + 1 ], "icon": "Contenido.png", "implies": "PHP", @@ -1875,11 +2072,11 @@ }, "website": "http://contenido.org/en" }, - "Contens": { + "Contensis": { "cats": [ - "1" + 1 ], - "icon": "Contens.png", + "icon": "Contensis.png", "implies": [ "Java", "CFML" @@ -1887,12 +2084,12 @@ "meta": { "generator": "Contensis CMS Version ([\\d.]+)\\;version:\\1" }, - "website": "http://www.contens.com/en/pub/index.cfm" + "website": "https://zengenti.com/en-gb/products/contensis" }, "ContentBox": { "cats": [ - "1", - "11" + 1, + 11 ], "icon": "ContentBox.png", "implies": "Adobe ColdFusion", @@ -1903,15 +2100,15 @@ }, "Contentful": { "cats": [ - "1" + 1 ], - "html": "<[^>]+(?:https?:)?//(?:assets|downloads|images|videos)\\.contentful\\.com", + "html": "<[^>]+(?:https?:)?//(?:assets|downloads|images|videos)\\.(?:ct?fassets\\.net|contentful\\.com)", "icon": "Contentful.svg", "website": "http://www.contentful.com" }, "ConversionLab": { "cats": [ - "10" + 10 ], "icon": "ConversionLab.png", "script": "conversionlab\\.trackset\\.com/track/tsend\\.js", @@ -1919,7 +2116,7 @@ }, "Coppermine": { "cats": [ - "7" + 7 ], "html": "", + "website": "https://www.docker.com/" }, "Dojo": { "cats": [ - "12" + 59 ], + "icon": "Dojo.png", "js": { "dojo": "", - "dojo.version.major": "(.*)\\;version:\\1" + "dojo.version.major": "^(.+)$\\;version:\\1" }, - "icon": "Dojo.png", "script": "([\\d.]+)/dojo/dojo(?:\\.xd)?\\.js\\;version:\\1", - "website": "http://dojotoolkit.org" + "website": "https://dojotoolkit.org" }, "Dokeos": { "cats": [ - "21" + 21 ], "headers": { "X-Powered-By": "Dokeos" @@ -2389,25 +2620,29 @@ "meta": { "generator": "Dokeos" }, - "website": "http://dokeos.com" + "website": "https://dokeos.com" }, "DokuWiki": { "cats": [ - "8" + 8 ], - "headers": { - "Set-Cookie": "^DokuWiki=" + "cookies": { + "DokuWiki": "" }, + "html": [ + "]+id=\"dokuwiki__>", + "]+href=\"#dokuwiki__" + ], "icon": "DokuWiki.png", "implies": "PHP", "meta": { - "generator": "^DokuWiki( Release [\\-\\d]+)?\\;version:\\1" + "generator": "^DokuWiki( Release [\\d-]+)?\\;version:\\1" }, - "website": "http://www.dokuwiki.org" + "website": "https://www.dokuwiki.org" }, "Dotclear": { "cats": [ - "1" + 1 ], "headers": { "X-Dotclear-Static-Cache": "" @@ -2418,7 +2653,7 @@ }, "DoubleClick Ad Exchange (AdX)": { "cats": [ - "36" + 36 ], "icon": "DoubleClick.svg", "script": [ @@ -2430,7 +2665,7 @@ }, "DoubleClick Campaign Manager (DCM)": { "cats": [ - "36" + 36 ], "icon": "DoubleClick.svg", "script": "2mdn\\.net", @@ -2438,7 +2673,7 @@ }, "DoubleClick Floodlight": { "cats": [ - "36" + 36 ], "icon": "DoubleClick.svg", "script": "https?://fls\\.doubleclick\\.net", @@ -2446,7 +2681,7 @@ }, "DoubleClick for Publishers (DFP)": { "cats": [ - "36" + 36 ], "icon": "DoubleClick.svg", "script": "googletagservices\\.com/tag/js/gpt(?:_mobile)?\\.js", @@ -2454,7 +2689,7 @@ }, "DovetailWRP": { "cats": [ - "1" + 1 ], "html": "]* href=\"\\/DovetailWRP\\/", "icon": "DovetailWRP.png", @@ -2464,7 +2699,7 @@ }, "Doxygen": { "cats": [ - "4" + 4 ], "html": "(?:|" ], "icon": "Google Tag Manager.png", + "js": { + "google_tag_manager": "", + "googletag": "" + }, "website": "http://www.google.com/tagmanager" }, "Google Wallet": { "cats": [ - "41" + 41 ], "icon": "Google Wallet.png", "script": [ @@ -3630,7 +3981,7 @@ }, "Google Web Server": { "cats": [ - "22" + 22 ], "headers": { "Server": "gws" @@ -3640,13 +3991,13 @@ }, "Google Web Toolkit": { "cats": [ - "18" + 18 ], + "icon": "Google Web Toolkit.png", + "implies": "Java", "js": { "__gwt_": "" }, - "icon": "Google Web Toolkit.png", - "implies": "Java", "meta": { "gwt:property": "" }, @@ -3654,10 +4005,10 @@ }, "Graffiti CMS": { "cats": [ - "1" + 1 ], - "headers": { - "Set-Cookie": "graffitibot[^;]=" + "cookies": { + "graffitibot": "" }, "icon": "Graffiti CMS.png", "implies": "Microsoft ASP.NET", @@ -3667,20 +4018,9 @@ "script": "/graffiti\\.js", "website": "http://graffiticms.codeplex.com" }, - "Grandstream": { - "cats": [ - "22", - "39" - ], - "headers": { - "Server": "Grandstream\\/?([\\d\\.]+)?\\;version:\\1" - }, - "icon": "Grandstream.png", - "website": "http://www.grandstream.com" - }, "Grav": { "cats": [ - "1" + 1 ], "icon": "Grav.png", "implies": "PHP", @@ -3691,18 +4031,18 @@ }, "Gravatar": { "cats": [ - "19" + 19 ], + "html": "<[^>]+gravatar\\.com/avatar/", + "icon": "Gravatar.png", "js": { "Gravatar": "" }, - "html": "<[^>]+gravatar\\.com/avatar/", - "icon": "Gravatar.png", "website": "http://gravatar.com" }, "Gravity Forms": { "cats": [ - "19" + 19 ], "html": [ "
]*gform_wrapper", @@ -3710,24 +4050,14 @@ "
-
+
+
+
+
+ + + + +
+
+
diff --git a/src/drivers/webextension/images/pin-active.svg b/src/drivers/webextension/images/pin-active.svg new file mode 100644 index 000000000..12462f304 --- /dev/null +++ b/src/drivers/webextension/images/pin-active.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/drivers/webextension/images/pin.svg b/src/drivers/webextension/images/pin.svg new file mode 100644 index 000000000..20e42e6d0 --- /dev/null +++ b/src/drivers/webextension/images/pin.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/drivers/webextension/js/content.js b/src/drivers/webextension/js/content.js index 7ffb4ba12..6111d1830 100644 --- a/src/drivers/webextension/js/content.js +++ b/src/drivers/webextension/js/content.js @@ -1,69 +1,84 @@ /** global: browser */ /** global: XMLSerializer */ -if ( typeof browser !== 'undefined' && typeof document.body !== 'undefined' ) { +/* global browser, chrome */ +/* eslint-env browser */ + +function sendMessage(id, subject, callback) { + (chrome || browser).runtime.sendMessage({ + id, + subject, + source: 'content.js', + }, callback || (() => {})); +} + +if (typeof browser !== 'undefined' && typeof document.body !== 'undefined') { try { - var html = new XMLSerializer().serializeToString(document); + sendMessage('init', {}); + + // HTML + let html = new XMLSerializer().serializeToString(document); + + const chunks = []; + const maxCols = 2000; + const maxRows = 3000; + const rows = html.length / maxCols; + + let i; - if ( html.length > 50000 ) { - html = html.substring(0, 25000) + html.substring(html.length - 25000, html.length); + for (i = 0; i < rows; i += 1) { + if (i < maxRows / 2 || i > rows - maxRows / 2) { + chunks.push(html.slice(i * maxCols, (i + 1) * maxCols)); + } } + html = chunks.join('\n'); + + // Scripts const scripts = Array.prototype.slice .apply(document.scripts) .filter(script => script.src) - .map(script => script.src); + .map(script => script.src) + .filter(script => script.indexOf('data:text/javascript;') !== 0); - browser.runtime.sendMessage({ - id: 'analyze', - subject: { html, scripts }, - source: 'content.js' - }); + sendMessage('analyze', { html, scripts }); + // JavaScript variables const script = document.createElement('script'); script.onload = () => { - addEventListener('message', event => { - if ( event.data.id !== 'js' ) { + const onMessage = (event) => { + if (event.data.id !== 'js') { return; } - browser.runtime.sendMessage({ - id: 'analyze', - subject: { - js: event.data.js - }, - source: 'content.js' - }); - }, true); - - ( chrome || browser ).runtime.sendMessage({ - id: 'init_js', - subject: {}, - source: 'content.js' - }, response => { - if ( response ) { + window.removeEventListener('message', onMessage); + + sendMessage('analyze', { js: event.data.js }); + + script.remove(); + }; + + window.addEventListener('message', onMessage); + + sendMessage('get_js_patterns', {}, (response) => { + if (response) { postMessage({ id: 'patterns', - patterns: response.patterns - }, '*'); + patterns: response.patterns, + }, window.location.href); } }); }; - script.setAttribute('id', 'wappalyzer'); script.setAttribute('src', browser.extension.getURL('js/inject.js')); document.body.appendChild(script); } catch (e) { - log(e); + sendMessage('log', e); } } -function log(message) { - browser.runtime.sendMessage({ - id: 'log', - message, - source: 'content.js' - }); -} +// https://stackoverflow.com/a/44774834 +// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/executeScript#Return_value +undefined; // eslint-disable-line no-unused-expressions diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js index 61ce7c1a1..5d8962f17 100644 --- a/src/drivers/webextension/js/driver.js +++ b/src/drivers/webextension/js/driver.js @@ -2,31 +2,61 @@ * 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 headersCache = {}; -var categoryOrder = []; +const tabCache = {}; +const robotsTxtQueue = {}; + +let categoryOrder = []; -browser.tabs.onRemoved.addListener(tabId => { +browser.tabs.onRemoved.addListener((tabId) => { tabCache[tabId] = null; }); +function userAgent() { + const url = chrome.extension.getURL('/'); + + if (url.match(/^chrome-/)) { + return 'chrome'; + } + + if (url.match(/^moz-/)) { + return 'firefox'; + } + + if (url.match(/^ms-browser-/)) { + return 'edge'; + } +} + /** * Get a value from localStorage */ -function getOption(name, defaultValue) { - return new Promise((resolve, reject) => { - const callback = item => { - resolve(item.hasOwnProperty(name) ? item[name] : defaultValue); - }; - - browser.storage.local.get(name) - .then(callback) - .catch(error => wappalyzer.log(error, 'driver', 'error')); +function getOption(name, defaultValue = null) { + return new Promise(async (resolve, reject) => { + let value = defaultValue; + + try { + const option = await browser.storage.local.get(name); + + if (option[name] !== undefined) { + value = option[name]; + } + } catch (error) { + wappalyzer.log(error.message, 'driver', 'error'); + + return reject(error.message); + } + + return resolve(value); }); } @@ -34,11 +64,17 @@ function getOption(name, defaultValue) { * Set a value in localStorage */ function setOption(name, value) { - var option = {}; + return new Promise(async (resolve, reject) => { + try { + await browser.storage.local.set({ [name]: value }); + } catch (error) { + wappalyzer.log(error.message, 'driver', 'error'); - option[name] = value; + return reject(error.message); + } - browser.storage.local.set(option); + return resolve(); + }); } /** @@ -47,160 +83,118 @@ 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, }); } /** * Make a POST request */ -function post(url, body) { - fetch(url, { - method: 'POST', - body: JSON.stringify(body) - }) - .then(response => { - wappalyzer.log('POST ' + url + ': ' + response.status, 'driver'); - }) - .catch(error => { - wappalyzer.log('POST ' + url + ': ' + error, 'driver', 'error'); +async function post(url, body) { + try { + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(body), }); -} -fetch('../apps.json') - .then(response => { - return response.json(); - }) - .then(json => { - wappalyzer.apps = json.apps; - wappalyzer.categories = json.categories; + wappalyzer.log(`POST ${url}: ${response.status}`, 'driver'); + } catch (error) { + wappalyzer.log(`POST ${url}: ${error}`, 'driver', 'error'); + } +} - categoryOrder = Object.keys(wappalyzer.categories).sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority); +// Capture response headers +browser.webRequest.onCompleted.addListener(async (request) => { + const headers = {}; - wappalyzer.parseJsPatterns(); - }) - .catch(error => { - wappalyzer.log('GET apps.json: ' + error, 'driver', 'error'); - }); + if (request.responseHeaders) { + const url = wappalyzer.parseUrl(request.url); -// Version check -var version = browser.runtime.getManifest().version; + let tab; -getOption('version') - .then(previousVersion => { - if ( previousVersion === null ) { - openTab({ - url: wappalyzer.config.websiteURL + 'installed' - }); - } else if ( version !== previousVersion ) { - getOption('upgradeMessage', true) - .then(upgradeMessage => { - if ( upgradeMessage ) { - openTab({ - url: wappalyzer.config.websiteURL + 'upgraded?v' + version, - background: true - }); - } - }); + try { + [tab] = await browser.tabs.query({ url: [url.href] }); + } catch (error) { + wappalyzer.log(error, 'driver', 'error'); } - setOption('version', version); - }); + if (tab) { + request.responseHeaders.forEach((header) => { + const name = header.name.toLowerCase(); -// Run content script -var callback = tabs => { - tabs.forEach(tab => { - if ( tab.url.match(/^https?:\/\//) ) { - browser.tabs.executeScript(tab.id, { - file: 'js/content.js' + headers[name] = headers[name] || []; + + headers[name].push((header.value || header.binaryValue || '').toString()); }); + + if (headers['content-type'] && /\/x?html/.test(headers['content-type'][0])) { + wappalyzer.analyze(url, { headers }, { tab }); + } } - }) -}; + } +}, { urls: ['http://*/*', 'https://*/*'], types: ['main_frame'] }, ['responseHeaders']); -browser.tabs.query({}) - .then(callback) - .catch(error => wappalyzer.log(error, 'driver', 'error')); +// Listen for messages +browser.runtime.onMessage.addListener(async (message, sender) => { + if (message.id === undefined) { + return Promise.resolve(); + } -// Capture response headers -browser.webRequest.onCompleted.addListener(request => { - var responseHeaders = {}; + if (message.id !== 'log') { + wappalyzer.log(`Message${message.source ? ` from ${message.source}` : ''}: ${message.id}`, 'driver'); + } - if ( request.responseHeaders ) { - var url = wappalyzer.parseUrl(request.url); + const pinnedCategory = await getOption('pinnedCategory'); - request.responseHeaders.forEach(function(header) { - if ( !responseHeaders[header.name.toLowerCase()] ) { - responseHeaders[header.name.toLowerCase()] = [] - } - responseHeaders[header.name.toLowerCase()].push(header.value || '' + header.binaryValue); - }); + const url = wappalyzer.parseUrl(sender.tab ? sender.tab.url : ''); - if ( headersCache.length > 50 ) { - headersCache = {}; - } + const cookies = await browser.cookies.getAll({ domain: `.${url.hostname}` }); - if ( /text\/html/.test(responseHeaders['content-type'][0]) ) { - if ( headersCache[url.canonical] === undefined ) { - headersCache[url.canonical] = {}; - } + let response; - Object.keys(responseHeaders).forEach(header => { - headersCache[url.canonical][header] = responseHeaders[header].slice(); - }); - } - } -}, { urls: [ 'http://*/*', 'https://*/*' ], types: [ 'main_frame' ] }, [ 'responseHeaders' ]); + switch (message.id) { + case 'log': + wappalyzer.log(message.subject, message.source); -// Listen for messages -( chrome || browser ).runtime.onMessage.addListener((message, sender, sendResponse) => { - if ( typeof message.id != 'undefined' ) { - if ( message.id !== 'log' ) { - wappalyzer.log('Message received' + ( message.source ? ' from ' + message.source : '' ) + ': ' + message.id, 'driver'); - } + break; + case 'init': + wappalyzer.analyze(url, { cookies }, { tab: sender.tab }); - var response; + break; + case 'analyze': + wappalyzer.analyze(url, message.subject, { tab: sender.tab }); - switch ( message.id ) { - case 'log': - wappalyzer.log(message.message, message.source); + await setOption('hostnameCache', wappalyzer.hostnameCache); - break; - case 'analyze': - var url = wappalyzer.parseUrl(sender.tab.url); + break; + case 'ad_log': + wappalyzer.cacheDetectedAds(message.subject); - if ( headersCache[url.canonical] !== undefined ) { - message.subject.headers = headersCache[url.canonical]; - } + break; + case 'get_apps': + response = { + tabCache: tabCache[message.tab.id], + apps: wappalyzer.apps, + categories: wappalyzer.categories, + pinnedCategory, + termsAccepted: userAgent() === 'chrome' || await getOption('termsAccepted', false), + }; - wappalyzer.analyze(url, message.subject, { - tab: sender.tab - }); - - break; - case 'ad_log': - wappalyzer.cacheDetectedAds(message.subject); - - break; - case 'get_apps': - response = { - tabCache: tabCache[message.tab.id], - apps: wappalyzer.apps, - categories: wappalyzer.categories - }; - - break; - case 'init_js': - response = { - patterns: wappalyzer.jsPatterns - }; - - break; - default: - } + break; + case 'set_option': + await setOption(message.key, message.value); - sendResponse(response); + break; + case 'get_js_patterns': + response = { + patterns: wappalyzer.jsPatterns, + }; + + break; + default: } + + return Promise.resolve(response); }); wappalyzer.driver.document = document; @@ -209,128 +203,181 @@ wappalyzer.driver.document = document; * Log messages to console */ wappalyzer.driver.log = (message, source, type) => { - console.log('[wappalyzer ' + type + ']', '[' + source + ']', message); + const log = ['warn', 'error'].indexOf(type) !== -1 ? type : 'log'; + + console[log](`[wappalyzer ${type}]`, `[${source}]`, message); }; /** * Display apps */ -wappalyzer.driver.displayApps = (detected, meta, context) => { - var tab = context.tab; +wappalyzer.driver.displayApps = async (detected, meta, context) => { + const { tab } = context; - tabCache[tab.id] = tabCache[tab.id] || { detected: [] }; + if (tab === undefined) { + return; + } + + tabCache[tab.id] = tabCache[tab.id] || { + detected: [], + }; tabCache[tab.id].detected = detected; - if ( Object.keys(detected).length ) { - getOption('dynamicIcon', true) - .then(dynamicIcon => { - var appName, found = false; - - // Find the main application to display - categoryOrder.forEach(match => { - Object.keys(detected).forEach(appName => { - var app = detected[appName]; - - app.props.cats.forEach(category => { - if ( category === match && !found ) { - var icon = app.props.icon || 'default.svg'; - - if ( !dynamicIcon ) { - icon = 'default.svg'; - } - - if ( /\.svg$/i.test(icon) ) { - icon = 'converted/' + icon.replace(/\.svg$/, '.png'); - } - - try { - browser.pageAction.setIcon({ - tabId: tab.id, - path: '../images/icons/' + icon - }); - } catch(e) { - // Firefox for Android does not support setIcon see https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 - } - - found = true; - } + const pinnedCategory = await getOption('pinnedCategory'); + const dynamicIcon = await getOption('dynamicIcon', true); + + let found = false; + + // Find the main application to display + [pinnedCategory].concat(categoryOrder).forEach((match) => { + Object.keys(detected).forEach((appName) => { + const app = detected[appName]; + + app.props.cats.forEach((category) => { + if (category === match && !found) { + let icon = app.props.icon && dynamicIcon ? app.props.icon : 'default.svg'; + + if (/\.svg$/i.test(icon)) { + icon = `converted/${icon.replace(/\.svg$/, '.png')}`; + } + + try { + browser.pageAction.setIcon({ + tabId: tab.id, + path: `../images/icons/${icon}`, }); - }); - }); - - if ( typeof chrome !== 'undefined' ) { - // Browser polyfill doesn't seem to work here - chrome.pageAction.show(tab.id); - } else { - browser.pageAction.show(tab.id); + } catch (e) { + // Firefox for Android does not support setIcon see https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 + } + + found = true; } }); - } + }); + }); + + browser.pageAction.show(tab.id); }; /** * Fetch and cache robots.txt for host */ -wappalyzer.driver.getRobotsTxt = (host, secure = false) => { - return new Promise((resolve, reject) => { - getOption('tracking', true) - .then(tracking => { - if ( !tracking ) { - return resolve([]); - } +wappalyzer.driver.getRobotsTxt = async (host, secure = false) => { + if (robotsTxtQueue[host]) { + return robotsTxtQueue[host]; + } - getOption('robotsTxtCache') - .then(robotsTxtCache => { - robotsTxtCache = robotsTxtCache || {}; - - if ( host in robotsTxtCache ) { - resolve(robotsTxtCache[host]); - } else { - const url = 'http' + ( secure ? 's' : '' ) + '://' + host + '/robots.txt'; - - fetch('http' + ( secure ? 's' : '' ) + '://' + host + '/robots.txt') - .then(response => { - if ( !response.ok ) { - if ( response.status === 404 ) { - return ''; - } else { - throw 'GET ' + response.url + ' was not ok'; - } - } - - return response.text(); - }) - .then(robotsTxt => { - robotsTxtCache[host] = wappalyzer.parseRobotsTxt(robotsTxt); - - setOption('robotsTxtCache', robotsTxtCache); - - resolve(robotsTxtCache[host]); - }) - .catch(reject); - } - }); - }); + const tracking = await getOption('tracking', true); + const robotsTxtCache = await getOption('robotsTxtCache', {}); + + robotsTxtQueue[host] = new Promise(async (resolve) => { + if (!tracking) { + return resolve([]); + } + + if (host in robotsTxtCache) { + return resolve(robotsTxtCache[host]); + } + + const timeout = setTimeout(() => resolve([]), 3000); + + let response; + + try { + response = await fetch(`http${secure ? 's' : ''}://${host}/robots.txt`, { redirect: 'follow' }); + } catch (error) { + wappalyzer.log(error, 'driver', 'error'); + + return resolve([]); + } + + clearTimeout(timeout); + + const robotsTxt = response.ok ? await response.text() : ''; + + robotsTxtCache[host] = Wappalyzer.parseRobotsTxt(robotsTxt); + + await setOption('robotsTxtCache', robotsTxtCache); + + delete robotsTxtQueue[host]; + + return resolve(robotsTxtCache[host]); }); + + return robotsTxtQueue[host]; }; /** * Anonymously track detected applications for research purposes */ -wappalyzer.driver.ping = (hostnameCache, adCache) => { - getOption('tracking', true) - .then(tracking => { - if ( tracking ) { - if ( Object.keys(hostnameCache).length ) { - post('https://api.wappalyzer.com/ping/v1/', hostnameCache); - } +wappalyzer.driver.ping = async (hostnameCache = {}, adCache = []) => { + const tracking = await getOption('tracking', true); + const termsAccepted = userAgent() === 'chrome' || await getOption('termsAccepted', false); - if ( adCache.length ) { - post('https://ad.wappalyzer.com/log/wp/', adCache); - } + if (tracking && termsAccepted) { + if (Object.keys(hostnameCache).length) { + post('https://api.wappalyzer.com/ping/v1/', hostnameCache); + } - setOption('robotsTxtCache', {}); - } - }); + if (adCache.length) { + post('https://ad.wappalyzer.com/log/wp/', adCache); + } + + await setOption('robotsTxtCache', {}); + } }; + +// Init +(async () => { + // Technologies + try { + const response = await fetch('../apps.json'); + const json = await response.json(); + + wappalyzer.apps = json.apps; + wappalyzer.categories = json.categories; + } catch (error) { + wappalyzer.log(`GET apps.json: ${error.message}`, 'driver', 'error'); + } + + wappalyzer.parseJsPatterns(); + + categoryOrder = Object.keys(wappalyzer.categories) + .map(categoryId => parseInt(categoryId, 10)) + .sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority); + + // Version check + const { version } = browser.runtime.getManifest(); + const previousVersion = await getOption('version'); + const upgradeMessage = await getOption('upgradeMessage', true); + + if (previousVersion === null) { + openTab({ + url: `${wappalyzer.config.websiteURL}installed`, + }); + } else if (version !== previousVersion && upgradeMessage) { + openTab({ + url: `${wappalyzer.config.websiteURL}upgraded?v${version}`, + background: true, + }); + } + + await setOption('version', version); + + // Hostname cache + wappalyzer.hostnameCache = await getOption('hostnameCache', {}); + + // Run content script on all tabs + try { + const tabs = await browser.tabs.query({ url: ['http://*/*', 'https://*/*'] }); + + tabs.forEach((tab) => { + browser.tabs.executeScript(tab.id, { + file: '../js/content.js', + }); + }); + } catch (error) { + wappalyzer.log(error, 'driver', 'error'); + } +})(); diff --git a/src/drivers/webextension/js/iframe.js b/src/drivers/webextension/js/iframe.js deleted file mode 100644 index 2370a3eda..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 = '1511804838-25881-9878-26947-14879'; - 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 ac3938f2a..3e9a6658f 100644 --- a/src/drivers/webextension/js/inject.js +++ b/src/drivers/webextension/js/inject.js @@ -1,26 +1,50 @@ -(function() { - try { - addEventListener('message', (event => { - if ( event.data.id !== 'patterns' ) { +/* eslint-env browser */ + +(() => { + try { + const detectJs = (chain) => { + const properties = chain.split('.'); + + let value = properties.length ? window : null; + + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; + + if (value && value.hasOwnProperty(property)) { + value = value[property]; + } else { + value = null; + + break; + } + } + + return typeof value === 'string' || typeof value === 'number' ? value : !!value; + }; + + const onMessage = (event) => { + if (event.data.id !== 'patterns') { return; } + removeEventListener('message', onMessage); + const patterns = event.data.patterns || {}; const js = {}; - for ( let appName in patterns ) { - if ( patterns.hasOwnProperty(appName) ) { + for (const appName in patterns) { + if (patterns.hasOwnProperty(appName)) { js[appName] = {}; - for ( let chain in patterns[appName] ) { - if ( patterns[appName].hasOwnProperty(chain) ) { + 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 ) { + if (value && patterns[appName][chain].hasOwnProperty(index)) { js[appName][chain][index] = value; } } @@ -29,33 +53,11 @@ } } - postMessage({ id: 'js', js }, '*'); - }), false); - } catch(e) { - // Fail quietly - } - - function detectJs(chain) { - try { - const properties = chain.split('.'); + postMessage({ id: 'js', js }, window.location.href); + }; - var value = properties.length ? window : null; - - for ( let i = 0; i < properties.length; i ++ ) { - var property = properties[i]; - - if ( value.hasOwnProperty(property) ) { - value = value[property]; - } else { - value = null; - - break; - } - } - - return typeof value === 'string' ? value : !!value; - } catch(e) { - // Fail quietly - } + addEventListener('message', onMessage); + } 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..491d26257 --- /dev/null +++ b/src/drivers/webextension/js/lib/iframe.js @@ -0,0 +1,1142 @@ + + +(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: 'fe20291-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 = '1532387537-31575-24732-11173-32339'; + 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, win.location.href); + } + } + + 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) 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..ebae3bc7a --- /dev/null +++ b/src/drivers/webextension/js/lib/network.js @@ -0,0 +1,792 @@ +(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 (isChrome()) { + 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 fc69619f8..4666b0045 100644 --- a/src/drivers/webextension/js/options.js +++ b/src/drivers/webextension/js/options.js @@ -1,69 +1,96 @@ /** global: browser */ /** global: Wappalyzer */ +/* globals browser Wappalyzer */ +/* eslint-env browser */ const wappalyzer = new Wappalyzer(); -function getOption(name, defaultValue, callback) { - browser.storage.local.get(name) - .then(item => { - callback(item.hasOwnProperty(name) ? item[name] : defaultValue); - }); +/** + * Get a value from localStorage + */ +function getOption(name, defaultValue = null) { + return new Promise(async (resolve, reject) => { + let value = defaultValue; + + try { + const option = await browser.storage.local.get(name); + + if (option[name] !== undefined) { + value = option[name]; + } + } catch (error) { + wappalyzer.log(error.message, 'driver', 'error'); + + return reject(error.message); + } + + return resolve(value); + }); } +/** + * Set a value in localStorage + */ function setOption(name, value) { - var option = {}; + return new Promise(async (resolve, reject) => { + try { + await browser.storage.local.set({ [name]: value }); + } catch (error) { + wappalyzer.log(error.message, 'driver', 'error'); - option[name] = value; + return reject(error.message); + } - browser.storage.local.set(option); + return resolve(); + }); } -document.addEventListener('DOMContentLoaded', () => { - var nodes = document.querySelectorAll('[data-i18n]'); +document.addEventListener('DOMContentLoaded', async () => { + 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); }); document.querySelector('#github').addEventListener('click', () => { - open(wappalyzer.config.githubURL); + window.open(wappalyzer.config.githubURL); }); document.querySelector('#twitter').addEventListener('click', () => { - open(wappalyzer.config.twitterURL); + window.open(wappalyzer.config.twitterURL); }); document.querySelector('#wappalyzer').addEventListener('click', () => { - open(wappalyzer.config.websiteURL); + window.open(wappalyzer.config.websiteURL); }); - getOption('upgradeMessage', true, value => { - const el = document.querySelector('#option-upgrade-message'); + let el; + let value; - el.checked = value; + // Upgrade message + value = await getOption('upgradeMessage', true); - el.addEventListener('change', () => { - setOption('upgradeMessage', el.checked); - }); - }); + el = document.querySelector('#option-upgrade-message'); - getOption('dynamicIcon', true, value => { - const el = document.querySelector('#option-dynamic-icon'); + el.checked = value; - el.checked = value; + el.addEventListener('change', e => setOption('upgradeMessage', e.target.checked)); - el.addEventListener('change', () => { - setOption('dynamicIcon', el.checked); - }); - }); + // Dynamic icon + value = await getOption('dynamicIcon', true); - getOption('tracking', true, value => { - const el = document.querySelector('#option-tracking'); + el = document.querySelector('#option-dynamic-icon'); - el.checked = value; + el.checked = value; - el.addEventListener('change', () => { - setOption('tracking', el.checked); - }); - }); + el.addEventListener('change', e => setOption('dynamicIcon', e.target.checked)); + + // Tracking + value = await getOption('tracking', true); + + el = document.querySelector('#option-tracking'); + + el.checked = value; + + el.addEventListener('change', e => setOption('tracking', e.target.checked)); }); diff --git a/src/drivers/webextension/js/popup.js b/src/drivers/webextension/js/popup.js index 377934f97..58d14867e 100644 --- a/src/drivers/webextension/js/popup.js +++ b/src/drivers/webextension/js/popup.js @@ -1,132 +1,184 @@ +/* eslint-env browser */ +/* global browser, chrome, jsonToDOM */ + /** global: chrome */ /** global: browser */ +/** global: jsonToDOM */ -var func = tabs => { - ( chrome || browser ).runtime.sendMessage({ - id: 'get_apps', - tab: tabs[0], - source: 'popup.js' - }, response => { - replaceDomWhenReady(appsToDomTemplate(response)); - }); -}; +let pinnedCategory = null; +let termsAccepted = false; -browser.tabs.query({ active: true, currentWindow: true }) - .then(func) - .catch(console.error); +function slugify(string) { + return string.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/--+/g, '-').replace(/(?:^-|-$)/, ''); +} -function replaceDomWhenReady(dom) { - if ( /complete|interactive|loaded/.test(document.readyState) ) { - replaceDom(dom); - } else { - document.addEventListener('DOMContentLoaded', () => { - replaceDom(dom); - }); - } +function i18n() { + const nodes = document.querySelectorAll('[data-i18n]'); + + Array.prototype.forEach.call(nodes, (node) => { + node.innerHTML = browser.i18n.getMessage(node.dataset.i18n); + }); } 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]'); + i18n(); + + Array.from(document.querySelectorAll('.detected__category-pin-wrapper')).forEach((pin) => { + pin.addEventListener('click', () => { + const categoryId = parseInt(pin.dataset.categoryId, 10); + + if (categoryId === pinnedCategory) { + pin.className = 'detected__category-pin-wrapper'; - Array.prototype.forEach.call(nodes, node => { - node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n); + pinnedCategory = null; + } else { + const active = document.querySelector('.detected__category-pin-wrapper--active'); + + if (active) { + active.className = 'detected__category-pin-wrapper'; + } + + pin.className = 'detected__category-pin-wrapper detected__category-pin-wrapper--active'; + + pinnedCategory = categoryId; + } + + (chrome || browser).runtime.sendMessage({ + id: 'set_option', + key: 'pinnedCategory', + value: pinnedCategory, + }); + }); }); } +function replaceDomWhenReady(dom) { + if (/complete|interactive|loaded/.test(document.readyState)) { + replaceDom(dom); + } else { + document.addEventListener('DOMContentLoaded', () => { + replaceDom(dom); + }); + } +} + function appsToDomTemplate(response) { - var - appName, confidence, version, categories, - 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 ( 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 ( cat in categories ) { + for (const cat in categories) { const apps = []; - for ( appName in categories[cat].apps ) { - confidence = response.tabCache.detected[appName].confidenceTotal; - version = response.tabCache.detected[appName].version; + for (const appName in categories[cat].apps) { + const { confidence, version } = response.tabCache.detected[appName]; apps.push( [ 'a', { class: 'detected__app', target: '_blank', - href: 'https://www.wappalyzer.com/applications/' + 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 ? ' ' + version : '' ) + ( confidence < 100 ? ' (' + confidence + '% sure)' : '' ) - ] - ] + appName, + ], version ? [ + 'span', { + class: 'detected__app-version', + }, + version, + ] : null, confidence < 100 ? [ + 'span', { + class: 'detected__app-confidence', + }, + `${confidence}% sure`, + ] : null, + ], ); } template.push( [ 'div', { - class: 'detected__category' + class: 'detected__category', }, [ - 'a', { - class: 'detected__category-link', - target: '_blank', - href: 'https://www.wappalyzer.com/categories/' + slugify(response.categories[cat].name) + 'div', { + class: 'detected__category-name', }, [ - 'span', { - class: 'detected__category-name' + 'a', { + class: 'detected__category-link', + target: '_blank', + 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' : ''}`, + 'data-category-id': cat, + title: browser.i18n.getMessage('categoryPin'), + }, [ + 'img', { + class: 'detected__category-pin detected__category-pin--active', + src: '../images/pin-active.svg', + }, + ], [ + 'img', { + class: 'detected__category-pin detected__category-pin--inactive', + 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'), ], ]; } @@ -134,6 +186,39 @@ function appsToDomTemplate(response) { return template; } -function slugify(string) { - return string.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/--+/g, '-').replace(/(?:^-|-$)/, ''); -} +const func = (tabs) => { + (chrome || browser).runtime.sendMessage({ + id: 'get_apps', + tab: tabs[0], + source: 'popup.js', + }, (response) => { + pinnedCategory = response.pinnedCategory; + termsAccepted = response.termsAccepted; + + if (termsAccepted) { + replaceDomWhenReady(appsToDomTemplate(response)); + } else { + i18n(); + + const wrapper = document.querySelector('.terms__wrapper'); + + document.querySelector('.terms__accept').addEventListener('click', () => { + (chrome || browser).runtime.sendMessage({ + id: 'set_option', + key: 'termsAccepted', + value: true, + }); + + wrapper.classList.remove('terms__wrapper--active'); + + func(tabs); + }); + + wrapper.classList.add('terms__wrapper--active'); + } + }); +}; + +browser.tabs.query({ active: true, currentWindow: true }) + .then(func) + .catch(console.error); diff --git a/src/drivers/webextension/manifest.json b/src/drivers/webextension/manifest.json index 28cc5d124..d946555f6 100644 --- a/src/drivers/webextension/manifest.json +++ b/src/drivers/webextension/manifest.json @@ -1,75 +1,83 @@ { - "name": "Wappalyzer", - "short_name": "Wappalyzer", - "author": "Elbert Alias", - "homepage_url": "https://www.wappalyzer.com", - "description": "Identify web technologies", - "version": "5.4.3", - "default_locale": "en", - "manifest_version": 2, - "icons": { - "16": "images/icon_16.png", - "19": "images/icon_19.png", - "32": "images/icon_32.png", - "38": "images/icon_38.png", - "128": "images/icon_128.png" - }, - "page_action": { - "default_icon": { - "16": "images/icon_16.png", - "19": "images/icon_19.png", - "32": "images/icon_32.png", - "38": "images/icon_38.png", - "128": "images/icon_128.png" - }, - "default_title": "Wappalyzer", - "default_popup": "html/popup.html" - }, - "background": { - "page": "html/background.html" + "name": "Wappalyzer", + "short_name": "Wappalyzer", + "author": "Elbert Alias", + "homepage_url": "https://www.wappalyzer.com", + "description": "Identify web technologies", + "version": "5.7.2", + "default_locale": "en", + "manifest_version": 2, + "icons": { + "16": "images/icon_16.png", + "19": "images/icon_19.png", + "32": "images/icon_32.png", + "38": "images/icon_38.png", + "128": "images/icon_128.png" + }, + "page_action": { + "default_icon": { + "16": "images/icon_16.png", + "19": "images/icon_19.png", + "32": "images/icon_32.png", + "38": "images/icon_38.png", + "128": "images/icon_128.png" + }, + "default_title": "Wappalyzer", + "default_popup": "html/popup.html" + }, + "background": { + "page": "html/background.html" - }, - "content_scripts": [ - { - "matches": [ - "http://*/*", - "https://*/*" - ], - "js": [ - "node_modules/webextension-polyfill/dist/browser-polyfill.js", - "js/content.js" - ], - "run_at": "document_idle" - }, - { - "matches": [ + }, + "content_scripts": [ + { + "matches": [ "http://*/*", - "https://*/*" - ], - "js": [ - "js/iframe.js" - ], - "run_at": "document_start", - "all_frames": true - } - ], - "web_accessible_resources": [ - "js/inject.js" - ], - "options_page": "html/options.html", - "options_ui": { - "page": "html/options.html", - "open_in_tab": false - }, - "permissions": [ - "storage", - "tabs", - "webRequest", - "webNavigation", - "http://*/*", - "https://*/*" - ], - "content_security_policy": "script-src 'self'; object-src 'self'" + "https://*/*" + ], + "js": [ + "node_modules/webextension-polyfill/dist/browser-polyfill.js", + "js/content.js" + ], + "run_at": "document_idle" + }, + { + "matches": [ + "http://*/*", + "https://*/*" + ], + "exclude_matches": [ + "https://*.modirum.com/*", + "https://www.alphaecommerce.gr/*" + ], + "js": [ + "js/lib/iframe.js" + ], + "run_at": "document_start", + "all_frames": true + } + ], + "web_accessible_resources": [ + "js/inject.js" + ], + "options_ui": { + "page": "html/options.html", + "open_in_tab": false + }, + "permissions": [ + "cookies", + "storage", + "tabs", + "webRequest", + "webNavigation", + "http://*/*", + "https://*/*" + ], + "content_security_policy": "script-src 'self'; object-src 'self'", + "applications": { + "gecko": { + "id": "wappalyzer@crunchlabz.com", + "strict_min_version": "60.0" + } + } } - - diff --git a/src/drivers/webextension/npm-shrinkwrap.json b/src/drivers/webextension/npm-shrinkwrap.json new file mode 100644 index 000000000..7dfcf7d96 --- /dev/null +++ b/src/drivers/webextension/npm-shrinkwrap.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "webextension-polyfill": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.3.1.tgz", + "integrity": "sha512-ISB42vlgMyM7xE1u6pREeCqmmXjLsYu/nqAR8Dl/gIAnylb+KpRpvKbVkUYNFePhhXn0Obkkc3jasOII9ztUtg==" + } + } +} diff --git a/src/drivers/webextension/package.json b/src/drivers/webextension/package.json index f27b81af6..19c18bd9a 100644 --- a/src/drivers/webextension/package.json +++ b/src/drivers/webextension/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "webextension-polyfill": "^0.1.1" + "webextension-polyfill": "^0.3.1" } } diff --git a/src/drivers/webextension/yarn.lock b/src/drivers/webextension/yarn.lock index 955172b78..e2e1d507d 100644 --- a/src/drivers/webextension/yarn.lock +++ b/src/drivers/webextension/yarn.lock @@ -2,6 +2,6 @@ # yarn lockfile v1 -webextension-polyfill@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.1.1.tgz#1d172e59b9ee8706d5ce2c55eebfe0cf23972d70" +webextension-polyfill@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.2.1.tgz#cdfc9126033039f1713553157d35beff1d4d6f4a" diff --git a/src/icons/1C-Bitrix.png b/src/icons/1C-Bitrix.png index 0d9b16acf..d0236f87f 100644 Binary files a/src/icons/1C-Bitrix.png and b/src/icons/1C-Bitrix.png differ diff --git a/src/icons/1and1.svg b/src/icons/1and1.svg deleted file mode 100644 index 7fc5b1ed7..000000000 --- a/src/icons/1and1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/2z Project.png b/src/icons/2z Project.png deleted file mode 100644 index edc1f21e2..000000000 Binary files a/src/icons/2z Project.png and /dev/null differ diff --git a/src/icons/3DM.png b/src/icons/3DM.png deleted file mode 100644 index f3115c788..000000000 Binary files a/src/icons/3DM.png and /dev/null differ diff --git a/src/icons/3ware.png b/src/icons/3ware.png deleted file mode 100644 index f3115c788..000000000 Binary files a/src/icons/3ware.png and /dev/null differ diff --git a/src/icons/91app.png b/src/icons/91app.png new file mode 100644 index 000000000..3963238b1 Binary files /dev/null and b/src/icons/91app.png differ diff --git a/src/icons/A-Frame.svg b/src/icons/A-Frame.svg new file mode 100644 index 000000000..7ff8039aa --- /dev/null +++ b/src/icons/A-Frame.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/ADPLAN.png b/src/icons/ADPLAN.png new file mode 100644 index 000000000..2252edbb8 Binary files /dev/null and b/src/icons/ADPLAN.png differ diff --git a/src/icons/AMPcms.png b/src/icons/AMPcms.png deleted file mode 100644 index 1d528c84b..000000000 Binary files a/src/icons/AMPcms.png and /dev/null differ diff --git a/src/icons/ATEN.png b/src/icons/ATEN.png deleted file mode 100644 index 512bd415f..000000000 Binary files a/src/icons/ATEN.png and /dev/null differ diff --git a/src/icons/Accelerated-Mobile-Pages.svg b/src/icons/Accelerated-Mobile-Pages.svg new file mode 100644 index 000000000..23bcd2654 --- /dev/null +++ b/src/icons/Accelerated-Mobile-Pages.svg @@ -0,0 +1,12 @@ + + + + AMP-Brand-Blue-Icon + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/icons/Accessible Portal.png b/src/icons/Accessible Portal.png deleted file mode 100644 index 688c1122e..000000000 Binary files a/src/icons/Accessible Portal.png and /dev/null differ diff --git a/src/icons/Adyen.svg b/src/icons/Adyen.svg new file mode 100644 index 000000000..96d26b546 --- /dev/null +++ b/src/icons/Adyen.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/icons/AngularDart.svg b/src/icons/AngularDart.svg new file mode 100644 index 000000000..78c49125a --- /dev/null +++ b/src/icons/AngularDart.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/icons/AngularJS.svg b/src/icons/AngularJS.svg new file mode 100644 index 000000000..d0fd45bd8 --- /dev/null +++ b/src/icons/AngularJS.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/Ant Design.svg b/src/icons/Ant Design.svg new file mode 100644 index 000000000..e9f8c2a9d --- /dev/null +++ b/src/icons/Ant Design.svg @@ -0,0 +1,43 @@ + + + + Group 28 Copy 5 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/icons/Appcues.svg b/src/icons/Appcues.svg new file mode 100644 index 000000000..20a1a545e --- /dev/null +++ b/src/icons/Appcues.svg @@ -0,0 +1,13 @@ + + + + Group + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/icons/Arc Forum.png b/src/icons/Arc Forum.png deleted file mode 100644 index c01f4e466..000000000 Binary files a/src/icons/Arc Forum.png and /dev/null differ diff --git a/src/icons/Backtory.svg b/src/icons/Backtory.svg new file mode 100644 index 000000000..9fb2e0c57 --- /dev/null +++ b/src/icons/Backtory.svg @@ -0,0 +1,86 @@ + +image/svg+xml \ No newline at end of file diff --git a/src/icons/Bootstrap.png b/src/icons/Bootstrap.png new file mode 100644 index 000000000..44230c442 Binary files /dev/null and b/src/icons/Bootstrap.png differ diff --git a/src/icons/Bootstrap.svg b/src/icons/Bootstrap.svg deleted file mode 100644 index 2ee62447c..000000000 --- a/src/icons/Bootstrap.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/icons/Brightspot.svg b/src/icons/Brightspot.svg new file mode 100644 index 000000000..2720d780d --- /dev/null +++ b/src/icons/Brightspot.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/icons/Brother.png b/src/icons/Brother.png deleted file mode 100644 index 737a03288..000000000 Binary files a/src/icons/Brother.png and /dev/null differ diff --git a/src/icons/BugSense.png b/src/icons/BugSense.png deleted file mode 100644 index 13eafe257..000000000 Binary files a/src/icons/BugSense.png and /dev/null differ diff --git a/src/icons/C++.png b/src/icons/C++.png deleted file mode 100644 index bfb1f9856..000000000 Binary files a/src/icons/C++.png and /dev/null differ diff --git a/src/icons/CDN77.png b/src/icons/CDN77.png new file mode 100644 index 000000000..7300be9d5 Binary files /dev/null and b/src/icons/CDN77.png differ diff --git a/src/icons/CNV.png b/src/icons/CNV.png deleted file mode 100644 index d7e259224..000000000 Binary files a/src/icons/CNV.png and /dev/null differ diff --git a/src/icons/CO2Stats.png b/src/icons/CO2Stats.png deleted file mode 100644 index 47689c3be..000000000 Binary files a/src/icons/CO2Stats.png and /dev/null differ diff --git a/src/icons/CakePHP.png b/src/icons/CakePHP.png index baece763b..a3f838b9b 100644 Binary files a/src/icons/CakePHP.png and b/src/icons/CakePHP.png differ diff --git a/src/icons/Canon.png b/src/icons/Canon.png deleted file mode 100644 index 1f55e03b3..000000000 Binary files a/src/icons/Canon.png and /dev/null differ diff --git a/src/icons/Catwalk.png b/src/icons/Catwalk.png deleted file mode 100644 index 1f55e03b3..000000000 Binary files a/src/icons/Catwalk.png and /dev/null differ diff --git a/src/icons/PHPoole.png b/src/icons/Cecil.png similarity index 100% rename from src/icons/PHPoole.png rename to src/icons/Cecil.png diff --git a/src/icons/CenteHTTPd.png b/src/icons/CenteHTTPd.png deleted file mode 100644 index 4bf3a2707..000000000 Binary files a/src/icons/CenteHTTPd.png and /dev/null differ diff --git a/src/icons/Contens.png b/src/icons/Contensis.png similarity index 100% rename from src/icons/Contens.png rename to src/icons/Contensis.png diff --git a/src/icons/Dart.svg b/src/icons/Dart.svg new file mode 100644 index 000000000..ca5dcdad7 --- /dev/null +++ b/src/icons/Dart.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/src/icons/David Webbox.png b/src/icons/David Webbox.png deleted file mode 100644 index 16e72fa12..000000000 Binary files a/src/icons/David Webbox.png and /dev/null differ diff --git a/src/icons/Day.js.svg b/src/icons/Day.js.svg new file mode 100644 index 000000000..6ceade70d --- /dev/null +++ b/src/icons/Day.js.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/Dell.svg b/src/icons/Dell.svg deleted file mode 100644 index a6863301c..000000000 --- a/src/icons/Dell.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/icons/Deployd.png b/src/icons/Deployd.png deleted file mode 100644 index f715e47b4..000000000 Binary files a/src/icons/Deployd.png and /dev/null differ diff --git a/src/icons/Docker.svg b/src/icons/Docker.svg new file mode 100644 index 000000000..b8b16676e --- /dev/null +++ b/src/icons/Docker.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/icons/Drupal.png b/src/icons/Drupal.png deleted file mode 100644 index b9b13f84f..000000000 Binary files a/src/icons/Drupal.png and /dev/null differ diff --git a/src/icons/Drupal.svg b/src/icons/Drupal.svg new file mode 100644 index 000000000..b32678feb --- /dev/null +++ b/src/icons/Drupal.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/src/icons/E-Merchant.png b/src/icons/E-Merchant.png deleted file mode 100644 index 6c49851af..000000000 Binary files a/src/icons/E-Merchant.png and /dev/null differ diff --git a/src/icons/ESERV-10.png b/src/icons/ESERV-10.png deleted file mode 100644 index 3e2c434ef..000000000 Binary files a/src/icons/ESERV-10.png and /dev/null differ diff --git a/src/icons/EWS-NIC4.png b/src/icons/EWS-NIC4.png deleted file mode 100644 index 5803d6385..000000000 Binary files a/src/icons/EWS-NIC4.png and /dev/null differ diff --git a/src/icons/EasyEngine.png b/src/icons/EasyEngine.png new file mode 100644 index 000000000..737ecd1b7 Binary files /dev/null and b/src/icons/EasyEngine.png differ diff --git a/src/icons/ElementUI.svg b/src/icons/ElementUI.svg new file mode 100644 index 000000000..4c05f8d2b --- /dev/null +++ b/src/icons/ElementUI.svg @@ -0,0 +1,11 @@ + + + + element-logo + Created with Sketch. + + + + \ No newline at end of file diff --git a/src/icons/Elementor.png b/src/icons/Elementor.png new file mode 100644 index 000000000..87fc23e01 Binary files /dev/null and b/src/icons/Elementor.png differ diff --git a/src/icons/Envoy.png b/src/icons/Envoy.png new file mode 100644 index 000000000..5d69d481c Binary files /dev/null and b/src/icons/Envoy.png differ diff --git a/src/icons/ExagonConcept.svg b/src/icons/ExagonConcept.svg deleted file mode 100644 index baaae65d8..000000000 --- a/src/icons/ExagonConcept.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/Fastcommerce.png b/src/icons/Fastcommerce.png new file mode 100644 index 000000000..6ba20a3f3 Binary files /dev/null and b/src/icons/Fastcommerce.png differ diff --git a/src/icons/Fbits.png b/src/icons/Fbits.png new file mode 100644 index 000000000..f5253019e Binary files /dev/null and b/src/icons/Fbits.png differ diff --git a/src/icons/GoJS.png b/src/icons/GoJS.png new file mode 100644 index 000000000..5a91ade54 Binary files /dev/null and b/src/icons/GoJS.png differ diff --git a/src/icons/Google Maps.png b/src/icons/Google Maps.png index 1f90c694b..ecf471ef0 100644 Binary files a/src/icons/Google Maps.png and b/src/icons/Google Maps.png differ diff --git a/src/icons/Google Search Appliance.png b/src/icons/Google Search Appliance.png deleted file mode 100644 index 71bb47077..000000000 Binary files a/src/icons/Google Search Appliance.png and /dev/null differ diff --git a/src/icons/Google Sites.png b/src/icons/Google Sites.png index 6f74a7a75..3bcb4f4cc 100644 Binary files a/src/icons/Google Sites.png and b/src/icons/Google Sites.png differ diff --git a/src/icons/Grandstream.png b/src/icons/Grandstream.png deleted file mode 100644 index a347c58c9..000000000 Binary files a/src/icons/Grandstream.png and /dev/null differ diff --git a/src/icons/Gravity Insights.png b/src/icons/Gravity Insights.png deleted file mode 100644 index 57aeabc13..000000000 Binary files a/src/icons/Gravity Insights.png and /dev/null differ diff --git a/src/icons/Halo.svg b/src/icons/Halo.svg new file mode 100644 index 000000000..cce2a59db --- /dev/null +++ b/src/icons/Halo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/src/icons/Instabot.png b/src/icons/Instabot.png new file mode 100644 index 000000000..3b49095d3 Binary files /dev/null and b/src/icons/Instabot.png differ diff --git a/src/icons/Ionicons.png b/src/icons/Ionicons.png index ace620764..67497c434 100644 Binary files a/src/icons/Ionicons.png and b/src/icons/Ionicons.png differ diff --git a/src/icons/JC-HTTPD.png b/src/icons/JC-HTTPD.png deleted file mode 100644 index 1f55e03b3..000000000 Binary files a/src/icons/JC-HTTPD.png and /dev/null differ diff --git a/src/icons/KS_HTTP.png b/src/icons/KS_HTTP.png deleted file mode 100644 index 1f55e03b3..000000000 Binary files a/src/icons/KS_HTTP.png and /dev/null differ diff --git a/src/icons/Kajabi.svg b/src/icons/Kajabi.svg new file mode 100644 index 000000000..dd6f7cd66 --- /dev/null +++ b/src/icons/Kajabi.svg @@ -0,0 +1 @@ +KajabiKajabi Logo diff --git a/src/icons/Klarna.svg b/src/icons/Klarna.svg index 0bb061c85..56524058c 100644 --- a/src/icons/Klarna.svg +++ b/src/icons/Klarna.svg @@ -1 +1,15 @@ -klarna \ No newline at end of file + + + + + + + + + + + diff --git a/src/icons/Kubernetes.svg b/src/icons/Kubernetes.svg new file mode 100644 index 000000000..bedd3b88e --- /dev/null +++ b/src/icons/Kubernetes.svg @@ -0,0 +1,84 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/icons/LOU.png b/src/icons/LOU.png new file mode 100644 index 000000000..c2836cab4 Binary files /dev/null and b/src/icons/LOU.png differ diff --git a/src/icons/MOBOTIX.png b/src/icons/MOBOTIX.png deleted file mode 100644 index a4574629c..000000000 Binary files a/src/icons/MOBOTIX.png and /dev/null differ diff --git a/src/icons/MakeShopKorea.png b/src/icons/MakeShopKorea.png new file mode 100644 index 000000000..5cf20bd0a Binary files /dev/null and b/src/icons/MakeShopKorea.png differ diff --git a/src/icons/Medium.svg b/src/icons/Medium.svg old mode 100644 new mode 100755 index afaa40a70..c8b251dde --- a/src/icons/Medium.svg +++ b/src/icons/Medium.svg @@ -1 +1,13 @@ - \ No newline at end of file + + + + Monogram + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/icons/Microsoft.png b/src/icons/Microsoft.png new file mode 100644 index 000000000..d6c37c10f Binary files /dev/null and b/src/icons/Microsoft.png differ diff --git a/src/icons/Microsoft.svg b/src/icons/Microsoft.svg deleted file mode 100644 index ecb0ac592..000000000 --- a/src/icons/Microsoft.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/Mithril.svg b/src/icons/Mithril.svg new file mode 100644 index 000000000..04cb102f1 --- /dev/null +++ b/src/icons/Mithril.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/icons/Moxa.png b/src/icons/Moxa.png deleted file mode 100644 index 03a2e6f93..000000000 Binary files a/src/icons/Moxa.png and /dev/null differ diff --git a/src/icons/NOIX.png b/src/icons/NOIX.png deleted file mode 100644 index ae6f9fee6..000000000 Binary files a/src/icons/NOIX.png and /dev/null differ diff --git a/src/icons/Nedstat.png b/src/icons/Nedstat.png deleted file mode 100644 index 2ffb430b5..000000000 Binary files a/src/icons/Nedstat.png and /dev/null differ diff --git a/src/icons/Neonclear.png b/src/icons/Neonclear.png deleted file mode 100644 index 0e0dcc59c..000000000 Binary files a/src/icons/Neonclear.png and /dev/null differ diff --git a/src/icons/Netmonitor.png b/src/icons/Netmonitor.png deleted file mode 100644 index 45da0d733..000000000 Binary files a/src/icons/Netmonitor.png and /dev/null differ diff --git a/src/icons/Nuxt.js.svg b/src/icons/Nuxt.js.svg new file mode 100644 index 000000000..e0bd713f8 --- /dev/null +++ b/src/icons/Nuxt.js.svg @@ -0,0 +1,10 @@ + + Nuxt Logo + + + + + + + + diff --git a/src/icons/OmniTouch 8660 My Teamwork.png b/src/icons/OmniTouch 8660 My Teamwork.png deleted file mode 100644 index 012c09d7d..000000000 Binary files a/src/icons/OmniTouch 8660 My Teamwork.png and /dev/null differ diff --git a/src/icons/OpenUI5.png b/src/icons/OpenUI5.png index 1a3703019..17a2b943e 100644 Binary files a/src/icons/OpenUI5.png and b/src/icons/OpenUI5.png differ diff --git a/src/icons/Pagevamp.png b/src/icons/Pagevamp.png new file mode 100644 index 000000000..246558737 Binary files /dev/null and b/src/icons/Pagevamp.png differ diff --git a/src/icons/Parcel.png b/src/icons/Parcel.png new file mode 100644 index 000000000..f3028060f Binary files /dev/null and b/src/icons/Parcel.png differ diff --git a/src/icons/Pendo.svg b/src/icons/Pendo.svg new file mode 100644 index 000000000..fe7d070c0 --- /dev/null +++ b/src/icons/Pendo.svg @@ -0,0 +1,16 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/icons/Penguin.svg b/src/icons/Penguin.svg deleted file mode 100644 index edf330b32..000000000 --- a/src/icons/Penguin.svg +++ /dev/null @@ -1 +0,0 @@ -penguin 2 \ No newline at end of file diff --git a/src/icons/PerfSONAR-PS.png b/src/icons/PerfSONAR-PS.png deleted file mode 100644 index f5e1402ff..000000000 Binary files a/src/icons/PerfSONAR-PS.png and /dev/null differ diff --git a/src/icons/Petrojs.png b/src/icons/Petrojs.png deleted file mode 100644 index a44eaa35c..000000000 Binary files a/src/icons/Petrojs.png and /dev/null differ diff --git a/src/icons/Piano Solo.png b/src/icons/Piano Solo.png deleted file mode 100644 index 1d4be842c..000000000 Binary files a/src/icons/Piano Solo.png and /dev/null differ diff --git a/src/icons/PlatformOS.svg b/src/icons/PlatformOS.svg new file mode 100644 index 000000000..d0f4281e2 --- /dev/null +++ b/src/icons/PlatformOS.svg @@ -0,0 +1 @@ + diff --git a/src/icons/Plura.png b/src/icons/Plura.png deleted file mode 100644 index de3113639..000000000 Binary files a/src/icons/Plura.png and /dev/null differ diff --git a/src/icons/Prospector.png b/src/icons/Prospector.png deleted file mode 100644 index 2a694ae38..000000000 Binary files a/src/icons/Prospector.png and /dev/null differ diff --git a/src/icons/Public CMS.png b/src/icons/Public CMS.png index 9a8e084ed..e5e51c364 100644 Binary files a/src/icons/Public CMS.png and b/src/icons/Public CMS.png differ diff --git a/src/icons/RakutenDigitalCommerce.png b/src/icons/RakutenDigitalCommerce.png new file mode 100644 index 000000000..8784b5b3c Binary files /dev/null and b/src/icons/RakutenDigitalCommerce.png differ diff --git a/src/icons/Rocket.svg b/src/icons/Rocket.svg new file mode 100644 index 000000000..feadcf82b --- /dev/null +++ b/src/icons/Rocket.svg @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/src/icons/SPIP.png b/src/icons/SPIP.png deleted file mode 100644 index 6054c483d..000000000 Binary files a/src/icons/SPIP.png and /dev/null differ diff --git a/src/icons/Sentinel.png b/src/icons/Sentinel.png deleted file mode 100644 index 9fa007fc2..000000000 Binary files a/src/icons/Sentinel.png and /dev/null differ diff --git a/src/icons/Sentry.svg b/src/icons/Sentry.svg new file mode 100644 index 000000000..53b613be4 --- /dev/null +++ b/src/icons/Sentry.svg @@ -0,0 +1 @@ +sentry-glyph-black \ No newline at end of file diff --git a/src/icons/Shopalize.png b/src/icons/Shopalize.png deleted file mode 100644 index c73389954..000000000 Binary files a/src/icons/Shopalize.png and /dev/null differ diff --git a/src/icons/Shopcada.png b/src/icons/Shopcada.png new file mode 100644 index 000000000..c27bef7d1 Binary files /dev/null and b/src/icons/Shopcada.png differ diff --git a/src/icons/Shoperfa.png b/src/icons/Shoperfa.png new file mode 100644 index 000000000..b6bc62af8 Binary files /dev/null and b/src/icons/Shoperfa.png differ diff --git a/src/icons/Sqreen.png b/src/icons/Sqreen.png new file mode 100644 index 000000000..c4e1000e6 Binary files /dev/null and b/src/icons/Sqreen.png differ diff --git a/src/icons/Stamplay.png b/src/icons/Stamplay.png deleted file mode 100644 index 8f5b25759..000000000 Binary files a/src/icons/Stamplay.png and /dev/null differ diff --git a/src/icons/StatCounter.png b/src/icons/StatCounter.png deleted file mode 100644 index e3d2bcc3d..000000000 Binary files a/src/icons/StatCounter.png and /dev/null differ diff --git a/src/icons/Statcounter.svg b/src/icons/Statcounter.svg new file mode 100644 index 000000000..9f91885fe --- /dev/null +++ b/src/icons/Statcounter.svg @@ -0,0 +1 @@ + diff --git a/src/icons/Strapi.png b/src/icons/Strapi.png new file mode 100644 index 000000000..065887ab7 Binary files /dev/null and b/src/icons/Strapi.png differ diff --git a/src/icons/TeamCity.png b/src/icons/TeamCity.png deleted file mode 100644 index 405f3f779..000000000 Binary files a/src/icons/TeamCity.png and /dev/null differ diff --git a/src/icons/TeamCity.svg b/src/icons/TeamCity.svg new file mode 100644 index 000000000..6083e574b --- /dev/null +++ b/src/icons/TeamCity.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/Tilda.svg b/src/icons/Tilda.svg new file mode 100644 index 000000000..d3c193bcb --- /dev/null +++ b/src/icons/Tilda.svg @@ -0,0 +1,18 @@ + + + + Page 1 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/icons/Tsoft.png b/src/icons/Tsoft.png deleted file mode 100644 index 04cc84c96..000000000 Binary files a/src/icons/Tsoft.png and /dev/null differ diff --git a/src/icons/UserGuiding.svg b/src/icons/UserGuiding.svg new file mode 100644 index 000000000..f01fa5fbc --- /dev/null +++ b/src/icons/UserGuiding.svg @@ -0,0 +1,42 @@ + + + + + UserGuiding .svg logo icon + + + + + diff --git a/src/icons/Vaadin.svg b/src/icons/Vaadin.svg new file mode 100644 index 000000000..6ebe998ee --- /dev/null +++ b/src/icons/Vaadin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/Virgool.svg b/src/icons/Virgool.svg new file mode 100644 index 000000000..40367e96c --- /dev/null +++ b/src/icons/Virgool.svg @@ -0,0 +1,14 @@ + + + Group 2 + Created with Sketch. + + + + + + + + + + diff --git a/src/icons/Volusion.png b/src/icons/Volusion.png deleted file mode 100644 index 20e6d109a..000000000 Binary files a/src/icons/Volusion.png and /dev/null differ diff --git a/src/icons/Volusion.svg b/src/icons/Volusion.svg new file mode 100644 index 000000000..34dd13831 --- /dev/null +++ b/src/icons/Volusion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/Vox.png b/src/icons/Vox.png deleted file mode 100644 index 8644dd574..000000000 Binary files a/src/icons/Vox.png and /dev/null differ diff --git a/src/icons/WP-Statistics.png b/src/icons/WP-Statistics.png new file mode 100644 index 000000000..22d1c580f Binary files /dev/null and b/src/icons/WP-Statistics.png differ diff --git a/src/icons/Web Optimizer.png b/src/icons/Web Optimizer.png deleted file mode 100644 index 64fe47fae..000000000 Binary files a/src/icons/Web Optimizer.png and /dev/null differ diff --git a/src/icons/WebsiteCreator.png b/src/icons/WebsiteCreator.png new file mode 100644 index 000000000..3c25e14aa Binary files /dev/null and b/src/icons/WebsiteCreator.png differ diff --git a/src/icons/Weglot.png b/src/icons/Weglot.png new file mode 100644 index 000000000..33da33a8a Binary files /dev/null and b/src/icons/Weglot.png differ diff --git a/src/icons/Wikispaces.png b/src/icons/Wikispaces.png deleted file mode 100644 index 91034ccf2..000000000 Binary files a/src/icons/Wikispaces.png and /dev/null differ diff --git a/src/icons/WindowsServer.png b/src/icons/WindowsServer.png new file mode 100644 index 000000000..31f92d9e8 Binary files /dev/null and b/src/icons/WindowsServer.png differ diff --git a/src/icons/Woosa.png b/src/icons/Woosa.png new file mode 100644 index 000000000..fbb0baaaa Binary files /dev/null and b/src/icons/Woosa.png differ diff --git a/src/icons/ZURB Foundation.png b/src/icons/ZURB Foundation.png index 994035248..2e8240fbc 100644 Binary files a/src/icons/ZURB Foundation.png and b/src/icons/ZURB Foundation.png differ diff --git a/src/icons/Zenfolio.png b/src/icons/Zenfolio.png new file mode 100644 index 000000000..56867d629 Binary files /dev/null and b/src/icons/Zenfolio.png differ diff --git a/src/icons/acquia-cloud.png b/src/icons/acquia-cloud.png new file mode 100644 index 000000000..d85843e9a Binary files /dev/null and b/src/icons/acquia-cloud.png differ diff --git a/src/icons/adobedmt.png b/src/icons/adobedmt.png new file mode 100644 index 000000000..0a74d4d36 Binary files /dev/null and b/src/icons/adobedmt.png differ diff --git a/src/icons/all-in-One-SEO-Pack.png b/src/icons/all-in-One-SEO-Pack.png new file mode 100644 index 000000000..16afdfa1b Binary files /dev/null and b/src/icons/all-in-One-SEO-Pack.png differ diff --git a/src/icons/aws-elb.png b/src/icons/aws-elb.png new file mode 100644 index 000000000..8db611796 Binary files /dev/null and b/src/icons/aws-elb.png differ diff --git a/src/icons/aws.svg b/src/icons/aws.svg new file mode 100644 index 000000000..48209aef8 --- /dev/null +++ b/src/icons/aws.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/bubble.png b/src/icons/bubble.png index 16ec01d91..a5d9479eb 100644 Binary files a/src/icons/bubble.png and b/src/icons/bubble.png differ diff --git a/src/icons/byINTI.svg b/src/icons/byINTI.svg new file mode 100644 index 000000000..98f626891 --- /dev/null +++ b/src/icons/byINTI.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/icons/chevereto.png b/src/icons/chevereto.png new file mode 100644 index 000000000..81e189814 Binary files /dev/null and b/src/icons/chevereto.png differ diff --git a/src/icons/coaster-cms.png b/src/icons/coaster-cms.png new file mode 100644 index 000000000..9ec4dcf1a Binary files /dev/null and b/src/icons/coaster-cms.png differ diff --git a/src/icons/colormeshop.png b/src/icons/colormeshop.png new file mode 100644 index 000000000..af70496d8 Binary files /dev/null and b/src/icons/colormeshop.png differ diff --git a/src/icons/datadome.png b/src/icons/datadome.png new file mode 100644 index 000000000..9c19fb1d7 Binary files /dev/null and b/src/icons/datadome.png differ diff --git a/src/icons/docusaurus.svg b/src/icons/docusaurus.svg new file mode 100644 index 000000000..81b7405ed --- /dev/null +++ b/src/icons/docusaurus.svg @@ -0,0 +1,35 @@ + + + + docusaurus + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/icons/eCommercePy.png b/src/icons/eCommercePy.png deleted file mode 100644 index ae9d304fb..000000000 Binary files a/src/icons/eCommercePy.png and /dev/null differ diff --git a/src/icons/eDevice SmartStack.png b/src/icons/eDevice SmartStack.png deleted file mode 100644 index efb8bac3f..000000000 Binary files a/src/icons/eDevice SmartStack.png and /dev/null differ diff --git a/src/icons/ensighten.png b/src/icons/ensighten.png new file mode 100644 index 000000000..935be0caa Binary files /dev/null and b/src/icons/ensighten.png differ diff --git a/src/icons/flarum.png b/src/icons/flarum.png new file mode 100644 index 000000000..fc3840330 Binary files /dev/null and b/src/icons/flarum.png differ diff --git a/src/icons/freshchat.png b/src/icons/freshchat.png new file mode 100644 index 000000000..b79a05767 Binary files /dev/null and b/src/icons/freshchat.png differ diff --git a/src/icons/futureshop.png b/src/icons/futureshop.png new file mode 100644 index 000000000..da3bdf84b Binary files /dev/null and b/src/icons/futureshop.png differ diff --git a/src/icons/gitea.svg b/src/icons/gitea.svg new file mode 100644 index 000000000..ac1594adb --- /dev/null +++ b/src/icons/gitea.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/google_cloud.svg b/src/icons/google_cloud.svg new file mode 100644 index 000000000..37faf439a --- /dev/null +++ b/src/icons/google_cloud.svg @@ -0,0 +1 @@ +Cloud_Logo_Nav diff --git a/src/icons/idosellshop.png b/src/icons/idosellshop.png new file mode 100644 index 000000000..f7e921195 Binary files /dev/null and b/src/icons/idosellshop.png differ diff --git a/src/icons/ionic.png b/src/icons/ionic.png new file mode 100644 index 000000000..f1b7f2ec5 Binary files /dev/null and b/src/icons/ionic.png differ diff --git a/src/icons/iplabel.svg b/src/icons/iplabel.svg new file mode 100644 index 000000000..b8c0f4079 --- /dev/null +++ b/src/icons/iplabel.svg @@ -0,0 +1,8 @@ +logo + + + + + + + diff --git a/src/icons/koha.png b/src/icons/koha.png new file mode 100644 index 000000000..3bdb0d688 Binary files /dev/null and b/src/icons/koha.png differ diff --git a/src/icons/laterpay.png b/src/icons/laterpay.png new file mode 100644 index 000000000..b425ec8e9 Binary files /dev/null and b/src/icons/laterpay.png differ diff --git a/src/icons/marked.svg b/src/icons/marked.svg new file mode 100644 index 000000000..a67fb80e5 --- /dev/null +++ b/src/icons/marked.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/icons/mkdocs.png b/src/icons/mkdocs.png new file mode 100644 index 000000000..279d831ff Binary files /dev/null and b/src/icons/mkdocs.png differ diff --git a/src/icons/mobirise.png b/src/icons/mobirise.png new file mode 100644 index 000000000..8244640ea Binary files /dev/null and b/src/icons/mobirise.png differ diff --git a/src/icons/paperjs.png b/src/icons/paperjs.png new file mode 100644 index 000000000..37cb8a114 Binary files /dev/null and b/src/icons/paperjs.png differ diff --git a/src/icons/parselecom.png b/src/icons/parselecom.png new file mode 100644 index 000000000..ba23f2e5b Binary files /dev/null and b/src/icons/parselecom.png differ diff --git a/src/icons/pelican.png b/src/icons/pelican.png new file mode 100644 index 000000000..3fffd4858 Binary files /dev/null and b/src/icons/pelican.png differ diff --git a/src/icons/polyfill.svg b/src/icons/polyfill.svg new file mode 100644 index 000000000..cfa655abb --- /dev/null +++ b/src/icons/polyfill.svg @@ -0,0 +1,6 @@ + diff --git a/src/icons/tilda.png b/src/icons/projesoft.png similarity index 60% rename from src/icons/tilda.png rename to src/icons/projesoft.png index 8d942ef57..77b9293da 100644 Binary files a/src/icons/tilda.png and b/src/icons/projesoft.png differ diff --git a/src/icons/raychat.png b/src/icons/raychat.png new file mode 100644 index 000000000..813d354dd Binary files /dev/null and b/src/icons/raychat.png differ diff --git a/src/icons/raychat.svg b/src/icons/raychat.svg deleted file mode 100644 index 708303307..000000000 --- a/src/icons/raychat.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/icons/redoc.png b/src/icons/redoc.png new file mode 100644 index 000000000..f99f5d48f Binary files /dev/null and b/src/icons/redoc.png differ diff --git a/src/icons/scrollreveal.svg b/src/icons/scrollreveal.svg new file mode 100644 index 000000000..8e0dd8e82 --- /dev/null +++ b/src/icons/scrollreveal.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/icons/shopline.png b/src/icons/shopline.png new file mode 100644 index 000000000..ebdb09d14 Binary files /dev/null and b/src/icons/shopline.png differ diff --git a/src/icons/signal.png b/src/icons/signal.png new file mode 100644 index 000000000..56ac41aa5 Binary files /dev/null and b/src/icons/signal.png differ diff --git a/src/icons/softtr.png b/src/icons/softtr.png new file mode 100644 index 000000000..65637e777 Binary files /dev/null and b/src/icons/softtr.png differ diff --git a/src/icons/spip.svg b/src/icons/spip.svg new file mode 100644 index 000000000..6650df09c --- /dev/null +++ b/src/icons/spip.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/sucuri.png b/src/icons/sucuri.png new file mode 100644 index 000000000..ec4be30a3 Binary files /dev/null and b/src/icons/sucuri.png differ diff --git a/src/icons/sympa.png b/src/icons/sympa.png new file mode 100644 index 000000000..288e92224 Binary files /dev/null and b/src/icons/sympa.png differ diff --git a/src/icons/tailwindcss.svg b/src/icons/tailwindcss.svg new file mode 100644 index 000000000..5b0424030 --- /dev/null +++ b/src/icons/tailwindcss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/transifex.png b/src/icons/transifex.png new file mode 100644 index 000000000..4c90c29a6 Binary files /dev/null and b/src/icons/transifex.png differ diff --git a/src/icons/tray.png b/src/icons/tray.png new file mode 100644 index 000000000..66d510765 Binary files /dev/null and b/src/icons/tray.png differ diff --git a/src/icons/webdev.png b/src/icons/webdev.png new file mode 100644 index 000000000..a37ce3ec8 Binary files /dev/null and b/src/icons/webdev.png differ diff --git a/src/icons/websocket.png b/src/icons/websocket.png new file mode 100644 index 000000000..7d94f1073 Binary files /dev/null and b/src/icons/websocket.png differ diff --git a/src/wappalyzer.js b/src/wappalyzer.js index 95c933eab..a50ddb913 100644 --- a/src/wappalyzer.js +++ b/src/wappalyzer.js @@ -6,20 +6,123 @@ * 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 = []; + const detectedApps = Object.assign({}, apps, detected); + + // Exclude app in detected apps only + Object.keys(detectedApps).forEach((appName) => { + const app = detectedApps[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.keys(this.confidence).forEach((id) => { + total += this.confidence[id]; + }); + + this.confidenceTotal = Math.min(total, 100); + + return this.confidenceTotal; + } +} + class Wappalyzer { constructor() { this.apps = {}; this.categories = {}; this.driver = {}; this.jsPatterns = {}; - this.detected = {}; this.hostnameCache = {}; this.adCache = []; @@ -35,79 +138,118 @@ class Wappalyzer { * Log messages to console */ log(message, source, type) { - this.driver.log(message, source || '', type || 'debug'); + if (this.driver.log) { + this.driver.log(message, source || '', type || 'debug'); + } } analyze(url, data, context) { - var apps = {}; - - if ( typeof data.html !== 'string' ) { - data.html = ''; - } - - if ( this.detected[url.canonical] === undefined ) { + const apps = {}; + const promises = []; + const startTime = new Date(); + const { + scripts, + cookies, + headers, + js, + } = data; + + let { html } = data; + + if (this.detected[url.canonical] === undefined) { this.detected[url.canonical] = {}; } + const metaTags = []; + // Additional information - const matches = data.html.match(/]*[: ]lang="([a-z]{2}((-|_)[A-Z]{2})?)"/i); + let language = null; - const language = matches && matches.length ? matches[1] : null; + if (html) { + if (typeof html !== 'string') { + html = ''; + } - 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]); + let matches = data.html.match(new RegExp(']*[: ]lang="([a-z]{2}((-|_)[A-Z]{2})?)"', 'i')); - var app = apps[appName]; + language = matches && matches.length ? matches[1] : null; - this.analyzeUrl(app, url); + // Meta tags + const regex = /]+>/ig; - if ( data.html ) { - this.analyzeHtml(app, data.html); - this.analyzeMeta(app, data.html); - } + do { + matches = regex.exec(html); + + if (!matches) { + break; + } + + metaTags.push(matches[0]); + } while (matches); + } + + 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]); - if ( data.scripts ) { - this.analyzeScripts(app, data.scripts); + const app = apps[appName]; + + promises.push(this.analyzeUrl(app, url)); + + if (html) { + promises.push(this.analyzeHtml(app, html)); + promises.push(this.analyzeMeta(app, metaTags)); } - if ( data.headers ) { - this.analyzeHeaders(app, data.headers); + if (scripts) { + promises.push(this.analyzeScripts(app, scripts)); } - if ( data.env ) { - this.analyzeEnv(app, data.env); + if (cookies) { + promises.push(this.analyzeCookies(app, cookies)); } - if ( data.robotsTxt ) { - this.analyzeRobotsTxt(app, data.robotsTxt); + if (headers) { + promises.push(this.analyzeHeaders(app, headers)); } - }) + }); - if ( data.js ) { - Object.keys(data.js).forEach(appName => { - 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])); + } }); } - Object.keys(apps).forEach(appName => { - var app = apps[appName]; + return new Promise(async (resolve) => { + await Promise.all(promises); - if ( !app.detected || !app.getConfidence() ) { - delete apps[app.name]; - } - }); + Object.keys(apps).forEach((appName) => { + const app = apps[appName]; - this.resolveExcludes(apps); - this.resolveImplies(apps, url.canonical); + if (!app.detected || !app.getConfidence()) { + delete apps[app.name]; + } + }); - this.cacheDetectedApps(apps, url.canonical); - this.trackDetectedApps(apps, url, language); + resolveExcludes(apps, this.detected[url]); + this.resolveImplies(apps, url.canonical); - if ( Object.keys(apps).length ) { - this.log(Object.keys(apps).length + ' apps detected: ' + Object.keys(apps).join(', ') + ' on ' + url.canonical, 'core'); - } + 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'); + + if (Object.keys(apps).length) { + this.log(`Identified ${Object.keys(apps).join(', ')} (${url.hostname})`, 'core'); + } - this.driver.displayApps(this.detected[url.canonical], { language }, context); + this.driver.displayApps(this.detected[url.canonical], { language }, context); + + return resolve(); + }); } /** @@ -121,33 +263,32 @@ class Wappalyzer { * */ robotsTxtAllows(url) { - return new Promise((resolve, reject) => { - var parsed = this.parseUrl(url); + return new Promise(async (resolve, reject) => { + const parsed = this.parseUrl(url); - if ( parsed.protocol !== 'http:' && parsed.protocol !== 'https:' ) { + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { return reject(); } - this.driver.getRobotsTxt(parsed.host, parsed.protocol === 'https:') - .then(robotsTxt => { - if (robotsTxt.some(disallowedPath => parsed.pathname.indexOf(disallowedPath) === 0)) { - return reject(); - } else { - return resolve(); - } - }); + const robotsTxt = await this.driver.getRobotsTxt(parsed.host, parsed.protocol === 'https:'); + + if (robotsTxt.some(disallowedPath => parsed.pathname.indexOf(disallowedPath) === 0)) { + return reject(); + } + + return 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; } @@ -155,22 +296,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.trim()); + + 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.trim()); - if ( matches ) { - disallow.push(matches[1]); - } + if (matches) { + disallow.push(matches[1]); } } }); @@ -182,61 +322,59 @@ class Wappalyzer { * */ ping() { - if ( Object.keys(this.hostnameCache).length + this.adCache.length > 50 ) { - this.driver.ping(this.hostnameCache, this.adCache); + if (Object.keys(this.hostnameCache).length > 100) { + this.driver.ping(this.hostnameCache); this.hostnameCache = {}; - this.adCache = []; } - } - /** - * Enclose string in array - */ - asArray(value) { - return value instanceof Array ? value : [ value ]; + if (this.adCache.length > 50) { + this.driver.ping({}, this.adCache); + + this.adCache = []; + } } /** * 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 - } catch (e) { + attrs.regex = new RegExp(attr.replace('/', '\\/'), 'i'); // Escape slashes in regular expression + } catch (error) { attrs.regex = new RegExp(); - this.log(e + ': ' + attr, 'error', 'core'); + this.log(`${error.message}: ${attr}`, 'error', 'core'); } } }); @@ -246,7 +384,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; } @@ -257,69 +395,51 @@ class Wappalyzer { * Parse JavaScript patterns */ parseJsPatterns() { - Object.keys(this.apps).forEach(appName => { - if ( this.apps[appName].js ) { + Object.keys(this.apps).forEach((appName) => { + if (this.apps[appName].js) { this.jsPatterns[appName] = this.parsePatterns(this.apps[appName].js); } }); } - resolveExcludes(apps) { - var excludes = []; - - // Exclude app in detected apps only - Object.keys(apps).forEach(appName => { - var 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 ) { - checkImplies = false; - - Object.keys(apps).forEach(appName => { - var app = apps[appName]; + const resolve = (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; - } + 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; - } + 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 ? implied.confidence / 100 : 1 ); - }); + // 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); }); - } - }); + }); + } + }; + + // Implied applications + // Run several passes as implied apps may imply other apps + while (checkImplies) { + checkImplies = false; + + Object.keys(apps).forEach(resolve); } } @@ -327,18 +447,19 @@ class Wappalyzer { * Cache detected applications */ cacheDetectedApps(apps, url) { - Object.keys(apps).forEach(appName => { - var 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 ) { + if (this.driver.ping instanceof Function) { this.ping(); } } @@ -347,41 +468,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(); @@ -391,215 +515,158 @@ class Wappalyzer { * Analyze URL */ analyzeUrl(app, url) { - var patterns = this.parsePatterns(app.props.url); + const patterns = this.parsePatterns(app.props.url); - if ( patterns.length ) { - patterns.forEach(pattern => { - if ( pattern.regex.test(url.canonical) ) { - this.addDetected(app, pattern, 'url', url.canonical); - } - }); + if (!patterns.length) { + return Promise.resolve(); } + + return asyncForEach(patterns, (pattern) => { + if (pattern.regex.test(url.canonical)) { + addDetected(app, pattern, 'url', url.canonical); + } + }); } /** * Analyze HTML */ analyzeHtml(app, html) { - var patterns = this.parsePatterns(app.props.html); + const patterns = this.parsePatterns(app.props.html); - if ( patterns.length ) { - patterns.forEach(pattern => { - if ( pattern.regex.test(html) ) { - this.addDetected(app, pattern, 'html', html); - } - }); + if (!patterns.length) { + return Promise.resolve(); } + + return asyncForEach(patterns, (pattern) => { + if (pattern.regex.test(html)) { + addDetected(app, pattern, 'html', html); + } + }); } /** * Analyze script tag */ analyzeScripts(app, scripts) { - var patterns = this.parsePatterns(app.props.script); + const patterns = this.parsePatterns(app.props.script); - if ( patterns.length ) { - patterns.forEach(pattern => { - var match; + if (!patterns.length) { + return Promise.resolve(); + } - 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); + } }); - } + }); } /** * Analyze meta tag */ - analyzeMeta(app, html) { - var regex = /]+>/ig; - var patterns = this.parsePatterns(app.props.meta); - var content; - var match; - - while ( patterns && ( match = regex.exec(html) ) ) { - for ( var meta in patterns ) { - if ( new RegExp('(name|property)=["\']' + meta + '["\']', 'i').test(match) ) { - content = match.toString().match(/content=("|')([^"']+)("|')/i); - - patterns[meta].forEach(pattern => { - if ( content && content.length === 4 && pattern.regex.test(content[2]) ) { - this.addDetected(app, pattern, 'meta', content[2], meta); + analyzeMeta(app, metaTags) { + const patterns = this.parsePatterns(app.props.meta); + const promises = []; + + if (!app.props.meta) { + return Promise.resolve(); + } + + metaTags.forEach((match) => { + Object.keys(patterns).forEach((meta) => { + const r = new RegExp(`(?:name|property)=["']${meta}["']`, 'i'); + + if (r.test(match)) { + const content = match.match(/content=("|')([^"']+)("|')/i); + + promises.push(asyncForEach(patterns[meta], (pattern) => { + if (content && content.length === 4 && pattern.regex.test(content[2])) { + addDetected(app, pattern, 'meta', content[2], meta); } - }); + })); } - } - } + }); + }); + + return Promise.all(promises); } /** - * analyze response headers + * Analyze response headers */ analyzeHeaders(app, headers) { - var patterns = this.parsePatterns(app.props.headers); + const patterns = this.parsePatterns(app.props.headers); + const promises = []; - if ( headers ) { - Object.keys(patterns).forEach(headerName => { - patterns[headerName].forEach(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); } }); } - }); - }); - } - } - - /** - * Analyze environment variables - */ - analyzeEnv(app, envs) { - var patterns = this.parsePatterns(app.props.env); - - if ( patterns.length ) { - patterns.forEach(pattern => { - Object.keys(envs).forEach(env => { - if ( pattern.regex.test(envs[env]) ) { - this.addDetected(app, pattern, 'env', envs[env]); - } - }) - }); - } - } - - /** - * Analyze JavaScript variables - */ - analyzeJs(app, results) { - Object.keys(results).forEach(string => { - Object.keys(results[string]).forEach(index => { - const pattern = this.jsPatterns[app.name][string][index]; - const value = results[string][index]; - - if ( pattern.regex.test(value) ) { - this.addDetected(app, pattern, 'js', value); - } - }); + })); + } }); - } - /** - * Analyze robots.txt - */ - analyzeRobotsTxt(app, robotsTxt) { - var patterns = this.parsePatterns(app.props.robotsTxt); - - if ( patterns.length ) { - patterns.forEach(pattern => { - if ( pattern.regex.test(robotsTxt) ) { - this.addDetected(app, pattern, 'robotsTxt', robotsTxt); - } - }); - } + return promises ? Promise.all(promises) : Promise.resolve(); } /** - * Mark application as detected, set confidence and version + * Analyze cookies */ - addDetected(app, pattern, type, value, key) { - app.detected = true; + analyzeCookies(app, cookies) { + const patterns = this.parsePatterns(app.props.cookies); + const promises = []; - // Set confidence level - app.confidence[type + ' ' + ( key ? key + ' ' : '' ) + pattern.regex] = pattern.confidence || 100; + Object.keys(patterns).forEach((cookieName) => { + if (typeof patterns[cookieName] !== 'function') { + const cookieNameLower = cookieName.toLowerCase(); - // Detect version number - if ( pattern.version ) { - var versions = []; - var version = pattern.version; - var matches = pattern.regex.exec(value); + promises.push(asyncForEach(patterns[cookieName], (pattern) => { + const cookie = cookies.find(_cookie => _cookie.name.toLowerCase() === cookieNameLower); - 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]); + if (cookie && pattern.regex.test(cookie.value)) { + addDetected(app, pattern, 'cookies', cookie.value, cookieName); } - - // Replace back references - version = version.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 = ''; + return promises ? Promise.all(promises) : Promise.resolve(); } /** - * Calculate confidence total + * Analyze JavaScript variables */ - getConfidence() { - var total = 0; + analyzeJs(app, results) { + const promises = []; - for ( var id in this.confidence ) { - total += this.confidence[id]; - } + 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)) { + addDetected(app, pattern, 'js', value, string); + } + })); + } + }); - return this.confidenceTotal = Math.min(total, 100); + return promises ? Promise.all(promises) : Promise.resolve(); } } -if ( typeof module === 'object' ) { +if (typeof module === 'object') { module.exports = Wappalyzer; } diff --git a/src/wappalyzer.spec.js b/src/wappalyzer.spec.js new file mode 100644 index 000000000..ea68c22e0 --- /dev/null +++ b/src/wappalyzer.spec.js @@ -0,0 +1,143 @@ +/* eslint-env mocha */ + +const { assert, expect } = require('chai'); +const Wappalyzer = require('../src/wappalyzer'); + +const appsJson = { + appUrl: { + url: 'test', + }, + appCookies: { + cookies: { + test: 'test', + }, + }, + appUppercaseCookies: { + cookies: { + Test: 'Test', + }, + }, + appHeaders: { + headers: { + 'X-Powered-By': 'test', + }, + }, + appHtml: { + html: 'test v(\\d)\\;confidence:50\\;version:\\1', + implies: 'appImplies', + excludes: 'appExcludes', + }, + appMeta: { + meta: { + generator: 'test', + }, + }, + appScript: { + script: 'test', + }, + appJs: { + js: { + key: 'value', + }, + }, + appImplies: { + }, + appExcludes: { + html: 'test', + }, +}; + +const driverData = { + cookies: [ + { + name: 'test', + value: 'test', + domain: '', + path: '', + }, + ], + headers: { + 'x-powered-by': [ + 'test', + ], + }, + html: ' html test v1', + scripts: [ + 'test', + ], + js: { + appJs: { + key: [ + 'value', + ], + }, + }, +}; + +describe('Wappalyzer', () => { + describe('#analyze()', () => { + let apps; + + before(async () => { + const wappalyzer = new Wappalyzer(); + + wappalyzer.apps = appsJson; + + wappalyzer.parseJsPatterns(); + + wappalyzer.driver.displayApps = (detected) => { + apps = detected; + }; + + await wappalyzer.analyze({ canonical: 'test' }, driverData); + }); + + it('should identify technologies using URLs', () => { + expect(apps).to.have.any.keys('appUrl'); + }); + + it('should identify technologies using HTML', () => { + expect(apps).to.have.any.keys('appHtml'); + }); + + it('should identify technologies using meta tags', () => { + expect(apps).to.have.any.keys('appMeta'); + }); + + it('should identify technologies using script URLs', () => { + expect(apps).to.have.any.keys('appScript'); + }); + + it('should identify technologies using headers', () => { + expect(apps).to.have.any.keys('appHeaders'); + }); + + it('should identify technologies using cookies', () => { + expect(apps).to.have.any.keys('appCookies'); + }); + + it('should identify technologies using uppercase named cookies', () => { + expect(apps).to.have.any.keys('appUppercaseCookies'); + }); + + it('should identify technologies using JavaScript', () => { + expect(apps).to.have.any.keys('appJs'); + }); + + it('should return the implied technology', () => { + expect(apps).to.have.any.keys('appImplies'); + }); + + it('should not return the excluded technology', () => { + expect(apps).to.not.have.any.keys('appExcludes'); + }); + + it('should return the confidence value', () => { + assert.equal(apps.appHtml.confidenceTotal, 50); + }); + + it('should return the version number', () => { + assert.equal(apps.appHtml.version, '1'); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index c87638c01..000000000 --- a/yarn.lock +++ /dev/null @@ -1,27 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -file-type@3.8.*: - version "3.8.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.8.0.tgz#bcadf6a8f624ebe4a10e5ad26727b6b93f16d78d" - -html-comment-regex@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" - -is-svg@2.0.*: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.0.1.tgz#f93ab3bf1d6bbca30e9753cd3485b1300eebc013" - dependencies: - html-comment-regex "^1.1.0" - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - -read-chunk@2.0.*: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.0.0.tgz#3246e877829116cec059674c4d5f300f7a9261f3" - dependencies: - pify "^2.3.0"