Completed
Push — master ( 7e3e66...97b928 )
by Elbert
01:07
created

w.resolveImplies   B

Complexity

Conditions 6
Paths 19

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
nc 19
nop 2
dl 0
loc 41
rs 8.439

1 Function

Rating   Name   Duplication   Size   Complexity  
B 0 20 7
1
/**
2
 * Wappalyzer v4
3
 *
4
 * Created by Elbert Alias <[email protected]>
5
 *
6
 * License: GPLv3 http://www.gnu.org/licenses/gpl-3.0.txt
7
 */
8
9
var wappalyzer = (function() {
10
	//'use strict';
11
12
	/**
13
	 * Application class
14
	 */
15
	var Application = function(app, detected) {
16
		this.app             = app;
17
		this.confidence      = {};
18
		this.confidenceTotal = 0;
19
		this.detected        = Boolean(detected);
20
		this.excludes        = [];
21
		this.version         = '';
22
		this.versions        = [];
23
	};
24
25
	Application.prototype = {
26
		/**
27
		 * Calculate confidence total
28
		 */
29
		getConfidence: function() {
30
			var total = 0, id;
31
32
			for ( id in this.confidence ) {
33
				total += this.confidence[id];
34
			}
35
36
			return this.confidenceTotal = Math.min(total, 100);
37
		},
38
39
		/**
40
		 * Resolve version number (find the longest version number that contains all shorter detected version numbers)
41
		 */
42
		getVersion: function() {
43
			var i, resolved;
44
45
			if ( !this.versions.length ) {
46
				return;
47
			}
48
49
			this.versions.sort(function(a, b) {
50
				return a.length - b.length;
51
			});
52
53
			resolved = this.versions[0];
54
55
			for ( i = 1; i < this.versions.length; i++ ) {
56
				if ( this.versions[i].indexOf(resolved) === -1 ) {
57
					break;
58
				}
59
60
				resolved = this.versions[i];
61
			}
62
63
			return this.version = resolved;
64
		},
65
66
		setDetected: function(pattern, type, value, key) {
67
			this.detected = true;
68
69
			// Set confidence level
70
			this.confidence[type + ' ' + ( key ? key + ' ' : '' ) + pattern.regex] = pattern.confidence ? pattern.confidence : 100;
71
72
			// Detect version number
73
			if ( pattern.version ) {
74
				var
75
					version = pattern.version,
76
					matches = pattern.regex.exec(value);
77
78
				if ( matches ) {
79
					matches.forEach(function(match, i) {
80
						// Parse ternary operator
81
						var ternary = new RegExp('\\\\' + i + '\\?([^:]+):(.*)$').exec(version);
82
83
						if ( ternary && ternary.length === 3 ) {
84
							w.log({ match: match, i: i, ternary: ternary });
85
86
							version = version.replace(ternary[0], match ? ternary[1] : ternary[2]);
87
88
							w.log({ version: version });
89
						}
90
91
						// Replace back references
92
						version = version.replace(new RegExp('\\\\' + i, 'g'), match ? match : '');
93
					});
94
95
					if ( version && this.versions.indexOf(version) < 0 ) {
96
						this.versions.push(version);
97
					}
98
99
					this.getVersion();
100
				}
101
			}
102
		}
103
	};
104
105
	/**
106
	 * Call driver functions
107
	 */
108
	var driver = function(func, args) {
109
		if ( typeof w.driver[func] !== 'function' ) {
110
			w.log('not implemented: w.driver.' + func, 'warn');
111
112
			return;
113
		}
114
115
		if ( func !== 'log' ) {
116
			w.log('w.driver.' + func);
117
		}
118
119
		return w.driver[func](args);
120
	};
121
122
	/**
123
	 * Parse apps.json patterns
124
	 */
125
	var parsePatterns = function(patterns) {
126
		var
127
			key,
128
			parsed = {};
129
130
		// Convert array to object containing array
131
		if ( patterns instanceof Array ) {
132
			patterns = { main: patterns }
133
		}
134
135
		// Convert string to object containing array containing string
136
		if ( typeof patterns === 'string' ) {
137
			patterns = { main: [ patterns ] };
138
		}
139
140
		for ( key in patterns ) {
141
			parsed[key] = [];
142
143
			// Convert string to array containing string
144
			if ( typeof patterns[key] === 'string' ) {
145
				patterns[key] = [ patterns[key] ];
146
			}
147
148
			patterns[key].forEach(function(pattern) {
149
				var attrs = {};
150
151
				pattern.split('\\;').forEach(function(attr, i) {
152
					if ( i ) {
153
						// Key value pairs
154
						attr = attr.split(':');
155
156
						if ( attr.length > 1 ) {
157
							attrs[attr.shift()] = attr.join(':');
158
						}
159
					} else {
160
						attrs.string = attr;
161
162
						try {
163
							attrs.regex = new RegExp(attr.replace('/', '\/'), 'i'); // Escape slashes in regular expression
164
						} catch (e) {
165
							attrs.regex = new RegExp();
166
167
							w.log(e + ': ' + attr, 'error');
168
						}
169
					}
170
				});
171
172
				parsed[key].push(attrs);
173
			});
174
		}
175
176
		// Convert back to array if the original pattern list was an array (or string)
177
		if ( parsed.hasOwnProperty('main') ) {
178
			parsed = parsed.main;
179
		}
180
181
		return parsed;
182
	};
183
184
	/**
185
	 * Main script
186
	 */
187
	var w = {
188
		apps:     {},
189
		cats:     null,
190
		ping:     { hostnames: {} },
191
		adCache:  [],
192
		detected: {},
193
194
		config: {
195
			websiteURL: 'https://wappalyzer.com/',
196
			twitterURL: 'https://twitter.com/Wappalyzer',
197
			githubURL:  'https://github.com/AliasIO/Wappalyzer',
198
		},
199
200
		/**
201
		 * Log messages to console
202
		 */
203
		log: function(message, type) {
204
			if ( type === undefined ) {
205
				type = 'debug';
206
			}
207
208
			if ( typeof message === 'object' ) {
209
				message = JSON.stringify(message);
210
			}
211
212
			driver('log', { message: message, type: type });
213
		},
214
215
		/**
216
		 * Initialize
217
		 */
218
		init: function() {
219
			w.log('w.init');
220
221
			// Checks
222
			if ( w.driver === undefined ) {
223
				w.log('no driver, exiting');
224
225
				return;
226
			}
227
228
			// Initialize driver
229
			driver('init');
230
		},
231
232
		/**
233
		 * Analyze the request
234
		 */
235
		analyze: function(hostname, url, data) {
236
			var
237
				app, confirmMatch, type,
238
				apps = {};
239
240
			w.log('w.analyze');
241
242
			if ( w.apps === undefined || w.categories === undefined ) {
243
				w.log('apps.json not loaded, check for syntax errors');
244
245
				return;
246
			}
247
248
			// Remove hash from URL
249
			data.url = url = url.split('#')[0];
250
251
			if ( typeof data.html !== 'string' ) {
252
				data.html = '';
253
			}
254
255
			if ( w.detected[url] === undefined ) {
256
				w.detected[url] = {};
257
			}
258
259
			for ( app in w.apps ) {
260
				apps[app] = w.detected[url] && w.detected[url][app] ? w.detected[url][app] : new Application(app);
261
262
				for ( type in w.apps[app] ) {
263
					confirmMatch = function(pattern, value, key) {
264
						apps[app].setDetected(pattern, type, value, key);
265
					}
266
267
					switch ( type ) {
268
						case 'url':
269
							if ( url ) {
270
								w.analyzeUrl(parsePatterns(w.apps[app][type]), url, confirmMatch);
271
							}
272
273
							break;
274
						case 'html':
275
							if ( data.html ) {
276
								w.analyzeHtml(parsePatterns(w.apps[app][type]), data.html, confirmMatch);
277
							}
278
279
							break;
280
						case 'script':
281
							if ( data.html ) {
282
								w.analyzeScript(parsePatterns(w.apps[app][type]), data.html, confirmMatch);
283
							}
284
285
							break;
286
						case 'meta':
287
							if ( data.html ) {
288
								w.analyzeMeta(parsePatterns(w.apps[app][type]), data.html, confirmMatch);
289
							}
290
291
							break;
292
						case 'headers':
293
							if ( data.hasOwnProperty('headers') && data.headers ) {
294
								w.analyzeHeaders(parsePatterns(w.apps[app][type]), data.headers, confirmMatch);
295
							}
296
297
							break;
298
						case 'env':
299
							if ( data.hasOwnProperty('env') && data.env ) {
300
								w.analyzeEnv(parsePatterns(w.apps[app][type]), data.env, confirmMatch);
301
							}
302
303
							break;
304
						default:
305
					}
306
				}
307
			}
308
309
			for ( app in apps ) {
310
				if ( !apps[app].detected ) {
311
					delete apps[app];
312
				}
313
			}
314
315
			w.resolveExcludes(apps);
316
			w.resolveImplies(apps, url);
317
318
			w.cacheDetectedApps(apps, url);
319
			w.trackDetectedApps(apps, url, hostname, data.html);
320
321
			w.log(Object.keys(apps).length + ' apps detected: ' + Object.keys(apps).join(', ') + ' on ' + url);
322
323
			driver('displayApps');
324
		},
325
326
		resolveExcludes: function(apps) {
327
			var
328
				app,
329
				excludes = [];
330
331
			// Exclude app in detected apps only
332
			for ( app in apps ) {
333
				if ( w.apps[app].excludes ) {
334
					if ( typeof w.apps[app].excludes === 'string' ) {
335
						w.apps[app].excludes = [ w.apps[app].excludes ];
336
					}
337
338
					w.apps[app].excludes.forEach(function(excluded) {
339
						excludes.push(excluded);
340
					});
341
				}
342
			}
343
344
			// Remove excluded applications
345
			for ( app in apps ) {
346
				if ( excludes.indexOf(app) !== -1 ) {
347
					delete apps[app];
348
				}
349
			}
350
		},
351
352
		resolveImplies: function(apps, url) {
353
			var checkImplies = true;
354
355
			// Implied applications
356
			// Run several passes as implied apps may imply other apps
357
			while ( checkImplies ) {
358
				checkImplies = false;
359
360
				for ( app in apps ) {
361
					confidence = apps[app].confidence;
0 ignored issues
show
Bug introduced by
The variable confidence seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.confidence.
Loading history...
362
363
					if ( w.apps[app] && w.apps[app].implies ) {
364
						// Cast strings to an array
365
						if ( typeof w.apps[app].implies === 'string' ) {
366
							w.apps[app].implies = [ w.apps[app].implies ];
367
						}
368
369
						w.apps[app].implies.forEach(function(implied) {
370
							implied = parsePatterns(implied)[0];
371
372
							if ( !w.apps[implied.string] ) {
373
								w.log('Implied application ' + implied.string + ' does not exist', 'warn');
374
375
								return;
376
							}
377
378
							if ( !apps.hasOwnProperty(implied.string) ) {
379
								apps[implied.string] = w.detected[url] && w.detected[url][implied.string] ? w.detected[url][implied.string] : new Application(implied.string, true);
380
381
								checkImplies = true;
382
							}
383
384
							// Apply app confidence to implied app
385
							for ( id in confidence ) {
0 ignored issues
show
Bug introduced by
The variable id seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.id.
Loading history...
386
								apps[implied.string].confidence[id + ' implied by ' + app] = confidence[id] * ( implied.confidence ? implied.confidence / 100 : 1 );
387
							}
388
						});
389
					}
390
				}
391
			}
392
		},
393
394
		/**
395
		 * Cache detected applications
396
		 */
397
		cacheDetectedApps: function(apps, url) {
398
			var app, key, confidence;
399
400
			for ( app in apps ) {
401
				confidence = apps[app].confidence;
402
403
				// Per URL
404
				w.detected[url][app] = apps[app];
405
406
				for ( key in confidence ) {
407
					w.detected[url][app].confidence[key] = confidence[key];
408
				}
409
			}
410
		},
411
412
		/**
413
		 * Track detected applications
414
		 */
415
		trackDetectedApps: function(apps, url, hostname, html) {
416
			var app, match;
417
418
			for ( app in apps ) {
419
				if ( w.detected[url][app].getConfidence() >= 100 ) {
420
					if ( /(www.)?((.+?)\.(([a-z]{2,3}\.)?[a-z]{2,6}))$/.test(hostname) && !/((local|dev(elopment)?|stag(e|ing)?|test(ing)?|demo(shop)?|admin|google|cache)\.|\/admin|\.local)/.test(url) ) {
421
						if ( !w.ping.hostnames.hasOwnProperty(hostname) ) {
422
							w.ping.hostnames[hostname] = {
423
								applications: {},
424
								meta: {}
425
							};
426
						}
427
428
						if ( !w.ping.hostnames[hostname].applications.hasOwnProperty(app) ) {
429
							w.ping.hostnames[hostname].applications[app] = {
430
								hits: 0
431
							};
432
						}
433
434
						w.ping.hostnames[hostname].applications[app].hits ++;
435
436
						if ( apps[app].version ) {
437
							w.ping.hostnames[hostname].applications[app].version = apps[app].version;
438
						}
439
					} else {
440
						w.log('Ignoring hostname "' + hostname + '"');
441
					}
442
				}
443
			}
444
445
			// Additional information
446
			if ( w.ping.hostnames.hasOwnProperty(hostname) ) {
447
				match = html.match(/<html[^>]*[: ]lang="([a-z]{2}((-|_)[A-Z]{2})?)"/i);
448
449
				if ( match && match.length ) {
450
					w.ping.hostnames[hostname].meta['language'] = match[1];
451
				}
452
			}
453
454
			if ( Object.keys(w.ping.hostnames).length >= 50 || w.adCache.length >= 50 ) {
455
				driver('ping');
456
			}
457
		},
458
459
		/**
460
		 * Analyze URL
461
		 */
462
		analyzeUrl: function(patterns, url, confirmMatch) {
463
			patterns.forEach(function(pattern) {
464
				if ( pattern.regex.test(url) ) {
465
					confirmMatch(pattern, url);
466
				}
467
			});
468
		},
469
470
		/**
471
		 * Analyze HTML
472
		 */
473
		analyzeHtml: function(patterns, html, confirmMatch) {
474
			patterns.forEach(function(pattern) {
475
				if ( pattern.regex.test(html) ) {
476
					confirmMatch(pattern, html);
477
				}
478
			});
479
		},
480
481
		/**
482
		 * Analyze script tag
483
		 */
484
		analyzeScript: function(patterns, html, confirmMatch) {
485
			var regex = new RegExp('<script[^>]+src=("|\')([^"\']+)', 'ig');
486
487
			patterns.forEach(function(pattern) {
488
				var match;
489
490
				while ( match = regex.exec(html) ) {
491
					if ( pattern.regex.test(match[2]) ) {
492
						confirmMatch(pattern, match[2]);
493
					}
494
				}
495
			});
496
		},
497
498
		/**
499
		 * Analyze meta tag
500
		 */
501
		analyzeMeta: function(patterns, html, confirmMatch) {
502
			var
503
				content, match, meta,
504
				regex = /<meta[^>]+>/ig;
505
506
			while ( match = regex.exec(html) ) {
507
				for ( meta in patterns ) {
508
					if ( new RegExp('(name|property)=["\']' + meta + '["\']', 'i').test(match) ) {
509
						content = match.toString().match(/content=("|')([^"']+)("|')/i);
510
511
						patterns[meta].forEach(function(pattern) {
512
							if ( content && content.length === 4 && pattern.regex.test(content[2]) ) {
513
								confirmMatch(pattern, content[2], meta);
514
							}
515
						});
516
					}
517
				}
518
			}
519
		},
520
521
		/**
522
		 * analyze response headers
523
		 */
524
		analyzeHeaders: function(patterns, headers, confirmMatch) {
525
			var header;
526
527
			for ( header in patterns ) {
528
				patterns[header].forEach(function(pattern) {
529
					header = header.toLowerCase();
530
531
					if ( headers.hasOwnProperty(header) && pattern.regex.test(headers[header]) ) {
532
						confirmMatch(pattern, headers[header], header);
533
					}
534
				});
535
			}
536
		},
537
538
		/**
539
		 * Analyze environment variables
540
		 */
541
		analyzeEnv: function(patterns, envs, confirmMatch) {
542
			var env;
543
544
			patterns.forEach(function(pattern) {
545
				for ( env in envs ) {
546
					if ( pattern.regex.test(envs[env]) ) {
547
						confirmMatch(pattern, envs[env]);
548
					}
549
				}
550
			});
551
		}
552
	};
553
554
	return w;
555
})();
556
557
// CommonJS package
558
// See http://wiki.commonjs.org/wiki/CommonJS
559
if ( typeof exports === 'object' ) {
560
	exports.wappalyzer = wappalyzer;
561
}
562