You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

335 lines
8.2 KiB

/**
* WebExtension driver
*/
/** global: browser */
/** global: Wappalyzer */
const wappalyzer = new Wappalyzer();
var tabCache = {};
var headersCache = {};
var categoryOrder = [];
browser.tabs.onRemoved.addListener(tabId => {
tabCache[tabId] = null;
});
/**
* 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'));
});
}
/**
* Set a value in localStorage
*/
function setOption(name, value) {
var option = {};
option[name] = value;
browser.storage.local.set(option);
}
/**
* Open a tab
*/
function openTab(args) {
browser.tabs.create({
url: args.url,
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');
});
}
fetch('../apps.json')
.then(response => {
return response.json();
})
.then(json => {
wappalyzer.apps = json.apps;
wappalyzer.categories = json.categories;
categoryOrder = Object.keys(wappalyzer.categories).sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority);
})
.catch(error => {
wappalyzer.log('GET apps.json: ' + error, 'driver', 'error');
});
// Version check
var version = browser.runtime.getManifest().version;
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
});
}
});
}
setOption('version', version);
});
// Run content script
var callback = tabs => {
tabs.forEach(tab => {
if ( tab.url.match(/^https?:\/\//) ) {
browser.tabs.executeScript(tab.id, {
file: 'js/content.js'
});
}
})
};
try {
browser.tabs.query({})
.then(callback)
.catch(error => wappalyzer.log(error, 'driver', 'error'));
} catch ( e ) {
browser.tabs.query({}, callback);
}
// Capture response headers
browser.webRequest.onCompleted.addListener(request => {
var responseHeaders = {};
if ( request.responseHeaders ) {
var url = wappalyzer.parseUrl(request.url);
request.responseHeaders.forEach(function(header) {
if ( !responseHeaders[header.name.toLowerCase()] ) {
responseHeaders[header.name.toLowerCase()] = []
}
responseHeaders[header.name.toLowerCase()].push(header.value || '' + header.binaryValue);
});
if ( headersCache.length > 50 ) {
headersCache = {};
}
if ( /text\/html/.test(responseHeaders['content-type'][0]) ) {
if ( headersCache[url.canonical] === undefined ) {
headersCache[url.canonical] = {};
}
Object.keys(responseHeaders).forEach(header => {
headersCache[url.canonical][header] = responseHeaders[header].slice();
});
}
}
}, { urls: [ 'http://*/*', 'https://*/*' ], types: [ 'main_frame' ] }, [ 'responseHeaders' ]);
// Listen for messages
( chrome || browser ).runtime.onMessage.addListener((message, sender, sendResponse) => {
if ( typeof message.id != 'undefined' ) {
if ( message.id !== 'log' ) {
wappalyzer.log('Message received from ' + message.source + ': ' + message.id, 'driver');
}
var response;
switch ( message.id ) {
case 'log':
wappalyzer.log(message.message, message.source);
break;
case 'analyze':
var url = wappalyzer.parseUrl(sender.tab.url);
if ( headersCache[url.canonical] !== undefined ) {
message.subject.headers = headersCache[url.canonical];
}
wappalyzer.analyze(url.hostname, url.canonical, 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 'JS_ready':
response = {
patterns: wappalyzer.parseJS()
};
break;
default:
}
sendResponse(response);
}
});
wappalyzer.driver.document = document;
/**
* Log messages to console
*/
wappalyzer.driver.log = (message, source, type) => {
console.log('[wappalyzer ' + type + ']', '[' + source + ']', message);
};
/**
* Display apps
*/
wappalyzer.driver.displayApps = (detected, context) => {
var tab = context.tab;
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');
}
browser.pageAction.setIcon({
tabId: tab.id,
path: '../images/icons/' + icon
});
found = true;
}
});
});
});
if ( typeof chrome !== 'undefined' ) {
// Browser polyfill doesn't seem to work here
chrome.pageAction.show(tab.id);
} else {
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([]);
}
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);
}
});
});
});
};
/**
* 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('http://ping.wappalyzer.com/v3/', hostnameCache);
}
if ( adCache.length ) {
post('https://ad.wappalyzer.com/log/wp/', adCache);
}
setOption('robotsTxtCache', {});
}
});
};