Completed
Push — master ( 3bda85...50818d )
by Elbert
37s
created

src/drivers/npm/driver.js   A

Size

Lines of Code 162

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
nc 1
dl 0
loc 162
rs 10
noi 6

1 Function

Rating   Name   Duplication   Size   Complexity  
B driver.js ➔ ??? 0 30 2
1
'use strict';
2
3
const Wappalyzer = require('./wappalyzer');
4
const request = require('request');
0 ignored issues
show
Unused Code introduced by
The constant request seems to be never used. Consider removing it.
Loading history...
5
const url = require('url');
6
const fs = require('fs');
7
const Browser = require('zombie');
8
9
const json = JSON.parse(fs.readFileSync(__dirname + '/apps.json'));
0 ignored issues
show
Compatibility introduced by
Consider using the path module for constructing paths since they are otherwise not cross-OS compatible.
Loading history...
10
11
class Driver {
12
  constructor(pageUrl, options) {
13
    this.options = Object.assign({}, {
14
      debug: false,
15
      delay: 500,
16
      maxDepth: 3,
17
      maxUrls: 10,
18
      maxWait: 3000,
19
      recursive: false,
20
      userAgent: 'Mozilla/5.0 (compatible; Wappalyzer)',
21
    }, options || {});
22
23
    this.options.debug = Boolean(this.options.debug);
24
    this.options.recursive = Boolean(this.options.recursive);
25
    this.options.delay = this.options.recursive ? parseInt(this.options.delay, 10) : 0;
26
    this.options.maxDepth = parseInt(this.options.maxDepth, 10);
27
    this.options.maxUrls = parseInt(this.options.maxUrls, 10);
28
    this.options.maxWait = parseInt(this.options.maxWait, 10);
29
30
    this.origPageUrl = url.parse(pageUrl);
31
    this.analyzedPageUrls = [];
32
    this.apps = [];
33
34
    this.wappalyzer = new Wappalyzer();
35
36
    this.wappalyzer.apps = json.apps;
37
    this.wappalyzer.categories = json.categories;
38
39
    this.wappalyzer.driver.log = (message, source, type) => this.log(message, source, type);
40
    this.wappalyzer.driver.displayApps = detected => this.displayApps(detected);
41
  }
42
43
  analyze() {
44
    return this.crawl(this.origPageUrl);
45
  }
46
47
  log(message, source, type) {
48
    this.options.debug && console.log('[wappalyzer ' + type + ']', '[' + source + ']', message);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
49
  }
50
51
  displayApps(detected) {
52
    Object.keys(detected).forEach(appName => {
53
      const app = detected[appName];
54
55
      var categories = [];
56
57
      app.props.cats.forEach(id => {
58
        var category = {};
59
60
        category[id] = json.categories[id].name;
61
62
        categories.push(category)
63
      });
64
65
      if ( !this.apps.some(detectedApp => detectedApp.name === app.name) ) {
66
        this.apps.push({
67
          name: app.name,
68
          confidence: app.confidenceTotal.toString(),
69
          version: app.version,
70
          icon: app.props.icon || 'default.svg',
71
          website: app.props.website,
72
          categories
73
        });
74
      }
75
    });
76
  }
77
78
  fetch(pageUrl, index, depth) {
79
    return new Promise(resolve => {
80
      // Return when the URL is a duplicate or maxUrls has been reached
81
      if ( this.analyzedPageUrls.indexOf(pageUrl.href) !== -1 || this.analyzedPageUrls.length >= this.options.maxUrls ) {
82
        return resolve();
83
      }
84
85
      this.analyzedPageUrls.push(pageUrl.href);
86
87
      this.wappalyzer.log('depth: ' + depth + '; delay: ' + ( this.options.delay * index ) + 'ms; url: ' + pageUrl.href, 'driver');
88
89
      const browser = new Browser({
90
        userAgent: this.options.userAgent,
91
        waitDuration: this.options.maxWait + 'ms',
92
      });
93
94
      this.sleep(this.options.delay * index)
95
        .then(() => {
96
          browser.visit(pageUrl.href, error => {
0 ignored issues
show
Unused Code introduced by
The parameter error is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
97
            if ( !browser.resources['0'] || !browser.resources['0'].response ) {
98
              this.wappalyzer.log('No response from server', 'browser', 'error');
99
100
              return resolve();
101
            }
102
103
            browser.wait()
104
              .catch(error => this.wappalyzer.log(error.message, 'browser', 'error'))
105
              .finally(() => {
106
                const headers = {};
107
108
                browser.resources['0'].response.headers._headers.forEach(header => {
109
                  if ( !headers[header[0]] ){
110
                    headers[header[0]] = [];
111
                  }
112
113
                  headers[header[0]].push(header[1]);
114
                });
115
116
                const vars = Object.getOwnPropertyNames(browser.window);
117
                const html = browser.html();
118
                const scripts = Array.prototype.slice
119
                  .apply(browser.document.scripts)
120
                  .filter(s => s.src)
121
                  .map(s => s.src);
122
123
                this.wappalyzer.analyze(pageUrl.hostname, pageUrl.href, {
124
                  headers,
125
                  html,
126
                  env: vars,
127
                  scripts
128
                });
129
130
                const links = browser.body.getElementsByTagName('a');
131
132
                resolve(links);
133
              });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
134
          });
135
        });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
136
    });
137
  }
138
139
  crawl(pageUrl, index = 1, depth = 1) {
140
    return new Promise(resolve => {
141
      this.fetch(pageUrl, index, depth)
142
        .then(links => {
143
          if ( links && Boolean(this.options.recursive) && depth < this.options.maxDepth ) {
144
            links = Array.from(links)
145
              .filter(link => link.hostname === this.origPageUrl.hostname)
146
              .map(link => { link.hash = ''; return link });
147
148
            return Promise.all(links.map((link, index) => this.crawl(link, index + 1, depth + 1)));
149
          } else {
150
            return Promise.resolve();
151
          }
152
        })
153
        .then(() => resolve(this.apps));
154
    });
155
  }
156
157
  sleep(ms) {
158
    return ms ? new Promise(resolve => setTimeout(resolve, ms)) : Promise.resolve();
159
  }
160
};
161
162
module.exports = Driver;
163