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.

353 lines
8.6 KiB

/**
* WebExtension driver
*/
/** global: browser */
/** global: Wappalyzer */
const wappalyzer = new Wappalyzer();
var tabCache = {};
var categoryOrder = [];
var options = {};
var robotsTxtQueue = {};
browser.tabs.onRemoved.addListener(tabId => {
tabCache[tabId] = null;
});
/**
* Get a value from localStorage
*/
function getOption(name, defaultValue = null) {
return new Promise((resolve, reject) => {
const callback = item => {
options[name] = item.hasOwnProperty(name) ? item[name] : defaultValue;
resolve(options[name]);
};
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);
options[name] = value;
}
/**
* 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;
wappalyzer.parseJsPatterns();
categoryOrder = Object.keys(wappalyzer.categories)
.map(categoryId => parseInt(categoryId, 10))
.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);
});
getOption('dynamicIcon', true);
getOption('pinnedCategory');
// Run content script on all tabs
browser.tabs.query({ url: [ 'http://*/*', 'https://*/*' ] })
.then(tabs => {
tabs.forEach(tab => {
browser.tabs.executeScript(tab.id, {
file: '../js/content.js'
});
})
})
.catch(error => wappalyzer.log(error, 'driver', 'error'));
// Capture response headers
browser.webRequest.onCompleted.addListener(request => {
const headers = {};
if ( request.responseHeaders ) {
const url = wappalyzer.parseUrl(request.url);
browser.tabs.query({ url: [ url.href ] })
.then(tabs => {
const tab = tabs[0] || null;
console.log({ url: url.canonical, tab });
if ( tab ) {
request.responseHeaders.forEach(header => {
const name = header.name.toLowerCase();
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 });
}
}
})
.catch(error => wappalyzer.log(error, 'driver', 'error'));
}
}, { 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' + ( message.source ? ' from ' + message.source : '' ) + ': ' + message.id, 'driver');
}
var url = wappalyzer.parseUrl(sender.tab ? sender.tab.url : '');
var response;
switch ( message.id ) {
case 'log':
wappalyzer.log(message.subject, message.source);
break;
case 'init':
browser.cookies.getAll({ domain: '.' + url.hostname })
.then(cookies => wappalyzer.analyze(url, { cookies }, { tab: sender.tab }));
break;
case 'analyze':
wappalyzer.analyze(url, message.subject, { tab: sender.tab });
break;
case 'ad_log':
wappalyzer.cacheDetectedAds(message.subject);
break;
case 'get_apps':
response = {
tabCache: tabCache[message.tab.id],
apps: wappalyzer.apps,
categories: wappalyzer.categories,
pinnedCategory: options.pinnedCategory,
};
break;
case 'set_option':
setOption(message.key, message.value);
break;
case 'get_js_patterns':
response = {
patterns: wappalyzer.jsPatterns
};
break;
default:
}
sendResponse(response);
}
return true;
});
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, meta, context) => {
var tab = context.tab;
if ( tab === undefined ) {
return;
}
tabCache[tab.id] = tabCache[tab.id] || {
detected: []
};
tabCache[tab.id].detected = detected;
if ( Object.keys(detected).length ) {
var appName, found = false;
// Find the main application to display
[ options.pinnedCategory ].concat(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 ( !options.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;
}
});
});
});
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) => {
if ( robotsTxtQueue.hasOwnProperty(host) ) {
return robotsTxtQueue[host];
}
robotsTxtQueue[host] = new Promise((resolve, reject) => {
getOption('tracking', true)
.then(tracking => {
if ( !tracking ) {
return resolve([]);
}
getOption('robotsTxtCache')
.then(robotsTxtCache => {
robotsTxtCache = robotsTxtCache || {};
if ( host in robotsTxtCache ) {
return resolve(robotsTxtCache[host]);
}
const timeout = setTimeout(() => resolve([]), 3000);
fetch('http' + ( secure ? 's' : '' ) + '://' + host + '/robots.txt', { redirect: 'follow' })
.then(response => {
clearTimeout(timeout);
return response.ok ? response.text() : '';
})
.then(robotsTxt => {
robotsTxtCache[host] = wappalyzer.parseRobotsTxt(robotsTxt);
setOption('robotsTxtCache', robotsTxtCache);
resolve(robotsTxtCache[host]);
})
.catch(err => resolve([]));
});
});
})
.finally(() => delete robotsTxtQueue[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);
}
if ( adCache.length ) {
post('https://ad.wappalyzer.com/log/wp/', adCache);
}
setOption('robotsTxtCache', {});
}
});
};