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.

415 lines
10 KiB

5 years ago
'use strict'
const Wappalyzer = {
technologies: [],
categories: [],
slugify(string) {
return string
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/--+/g, '-')
.replace(/(?:^-|-$)/, '')
},
getTechnology(name) {
return Wappalyzer.technologies.find(({ name: _name }) => name === _name)
},
getCategory(id) {
return Wappalyzer.categories.find(({ id: _id }) => id === _id)
},
/**
* Resolve promises for implied technology.
* @param {Array} detections
*/
5 years ago
resolve(detections = []) {
const resolved = detections.reduce((resolved, { technology }) => {
if (
resolved.findIndex(
({ technology: { name } }) => name === technology.name
) === -1
) {
let version = ''
let confidence = 0
detections
.filter(({ technology }) => technology)
.forEach(
({ technology: { name }, pattern, version: _version = '' }) => {
if (name === technology.name) {
confidence = Math.min(100, confidence + pattern.confidence)
version =
_version.length > version.length && _version.length <= 10
? _version
: version
}
4 years ago
}
)
6 years ago
5 years ago
resolved.push({ technology, confidence, version })
6 years ago
}
5 years ago
return resolved
}, [])
Wappalyzer.resolveExcludes(resolved)
Wappalyzer.resolveImplies(resolved)
const priority = ({ technology: { categories } }) =>
categories.reduce(
(max, id) => Math.max(max, Wappalyzer.getCategory(id).priority),
0
)
return resolved
.sort((a, b) => (priority(a) > priority(b) ? 1 : -1))
.map(
({
technology: { name, slug, categories, icon, website, cpe },
confidence,
4 years ago
version,
}) => ({
name,
slug,
categories: categories.map((id) => Wappalyzer.getCategory(id)),
confidence,
version,
icon,
website,
4 years ago
cpe,
})
)
5 years ago
},
/**
* Resolve promises for version of technology.
* @param {Promise} resolved
*/
5 years ago
resolveVersion({ version, regex }, match) {
let resolved = version
7 years ago
5 years ago
if (version) {
const matches = regex.exec(match)
7 years ago
5 years ago
if (matches) {
matches.forEach((match, index) => {
// Parse ternary operator
const ternary = new RegExp(`\\\\${index}\\?([^:]+):(.*)$`).exec(
version
)
5 years ago
if (ternary && ternary.length === 3) {
resolved = version.replace(
ternary[0],
match ? ternary[1] : ternary[2]
)
}
5 years ago
// Replace back references
resolved = resolved
.trim()
.replace(new RegExp(`\\\\${index}`, 'g'), match || '')
})
}
}
5 years ago
return resolved
},
7 years ago
/**
* Resolve promises for excluded technology.
* @param {Promise} resolved
*/
5 years ago
resolveExcludes(resolved) {
resolved.forEach(({ technology }) => {
technology.excludes.forEach(({ name }) => {
5 years ago
const excluded = Wappalyzer.getTechnology(name)
7 years ago
5 years ago
if (!excluded) {
throw new Error(`Excluded technology does not exist: ${name}`)
}
7 years ago
5 years ago
const index = resolved.findIndex(({ name }) => name === excluded.name)
4 years ago
if (index !== -1) {
5 years ago
resolved.splice(index, 1)
}
})
})
5 years ago
},
7 years ago
/**
* Resolve promises for implied technology.
* @param {Promise} resolved
*/
5 years ago
resolveImplies(resolved) {
let done = false
5 years ago
while (resolved.length && !done) {
resolved.forEach(({ technology, confidence }) => {
done = true
7 years ago
technology.implies.forEach(({ name, confidence: _confidence }) => {
5 years ago
const implied = Wappalyzer.getTechnology(name)
7 years ago
5 years ago
if (!implied) {
throw new Error(`Implied technology does not exist: ${name}`)
}
7 years ago
5 years ago
if (
resolved.findIndex(
({ technology: { name } }) => name === implied.name
) === -1
) {
resolved.push({
technology: implied,
confidence: Math.min(confidence, _confidence),
4 years ago
version: '',
})
7 years ago
5 years ago
done = false
}
})
})
7 years ago
}
5 years ago
},
/**
* Initialize analyzation.
* @param {*} param0
*/
analyze({ url, html, robots, meta, headers, certIssuer, cookies, scripts }) {
5 years ago
const oo = Wappalyzer.analyzeOneToOne
const om = Wappalyzer.analyzeOneToMany
const mm = Wappalyzer.analyzeManyToMany
const flatten = (array) => Array.prototype.concat.apply([], array)
try {
const detections = flatten(
Wappalyzer.technologies.map((technology) =>
flatten([
oo(technology, 'url', url),
oo(technology, 'html', html),
oo(technology, 'robots', robots),
oo(technology, 'certIssuer', certIssuer),
4 years ago
om(technology, 'scripts', scripts),
mm(technology, 'cookies', cookies),
mm(technology, 'meta', meta),
4 years ago
mm(technology, 'headers', headers),
])
5 years ago
)
).filter((technology) => technology)
5 years ago
return detections
} catch (error) {
throw new Error(error.message || error.toString())
7 years ago
}
5 years ago
},
/**
* Extract technologies from data collected.
* @param {*object} data
*/
5 years ago
setTechnologies(data) {
const transform = Wappalyzer.transformPatterns
Wappalyzer.technologies = Object.keys(data).reduce((technologies, name) => {
const {
cats,
url,
html,
robots,
5 years ago
meta,
headers,
certIssuer,
5 years ago
cookies,
scripts,
5 years ago
js,
implies,
excludes,
icon,
website,
4 years ago
cpe,
5 years ago
} = data[name]
technologies.push({
name,
categories: cats || [],
slug: Wappalyzer.slugify(name),
url: transform(url),
4 years ago
headers: transform(headers),
5 years ago
cookies: transform(cookies),
html: transform(html),
certIssuer: transform(certIssuer),
robots: transform(robots),
5 years ago
meta: transform(meta),
scripts: transform(scripts),
js: transform(js, true),
implies: transform(implies).map(({ value, confidence }) => ({
name: value,
4 years ago
confidence,
})),
excludes: transform(excludes).map(({ value }) => ({
4 years ago
name: value,
})),
5 years ago
icon: icon || 'default.svg',
website: website || null,
4 years ago
cpe: cpe || null,
5 years ago
})
7 years ago
5 years ago
return technologies
}, [])
},
/**
* Assign categories for data.
* @param {Object} data
*/
5 years ago
setCategories(data) {
Wappalyzer.categories = Object.keys(data)
.reduce((categories, id) => {
const category = data[id]
5 years ago
categories.push({
id: parseInt(id, 10),
slug: Wappalyzer.slugify(category.name),
4 years ago
...category,
5 years ago
})
5 years ago
return categories
}, [])
.sort(({ priority: a }, { priority: b }) => (a > b ? -1 : 0))
},
7 years ago
/**
* Extract information from regex pattern.
* @param {string|array} patterns
*/
transformPatterns(patterns, caseSensitive = false) {
5 years ago
if (!patterns) {
return []
7 years ago
}
7 years ago
5 years ago
const toArray = (value) => (Array.isArray(value) ? value : [value])
7 years ago
5 years ago
if (typeof patterns === 'string' || Array.isArray(patterns)) {
patterns = { main: patterns }
}
7 years ago
5 years ago
const parsed = Object.keys(patterns).reduce((parsed, key) => {
parsed[caseSensitive ? key : key.toLowerCase()] = toArray(
patterns[key]
).map((pattern) => {
const { value, regex, confidence, version } = pattern
5 years ago
.split('\\;')
.reduce((attrs, attr, i) => {
if (i) {
// Key value pairs
attr = attr.split(':')
if (attr.length > 1) {
attrs[attr.shift()] = attr.join(':')
}
} else {
attrs.value = attr
5 years ago
// Escape slashes in regular expression
attrs.regex = new RegExp(attr.replace(/\//g, '\\/'), 'i')
}
7 years ago
5 years ago
return attrs
}, {})
7 years ago
5 years ago
return {
value,
5 years ago
regex,
confidence: parseInt(confidence || 100, 10),
4 years ago
version: version || '',
}
5 years ago
})
5 years ago
return parsed
}, {})
7 years ago
5 years ago
return 'main' in parsed ? parsed.main : parsed
},
/**
* @todo describe
* @param {Object} technology
* @param {String} type
* @param {String} value
*/
5 years ago
analyzeOneToOne(technology, type, value) {
return technology[type].reduce((technologies, pattern) => {
if (pattern.regex.test(value)) {
4 years ago
technologies.push({
technology,
pattern,
4 years ago
version: Wappalyzer.resolveVersion(pattern, value),
4 years ago
})
}
7 years ago
5 years ago
return technologies
}, [])
},
/**
* @todo update
* @param {Object} technology
* @param {String} type
* @param {Array} items
*/
5 years ago
analyzeOneToMany(technology, type, items = []) {
4 years ago
return items.reduce((technologies, value) => {
const patterns = technology[type] || []
5 years ago
patterns.forEach((pattern) => {
if (pattern.regex.test(value)) {
4 years ago
technologies.push({
technology,
pattern,
4 years ago
version: Wappalyzer.resolveVersion(pattern, value),
4 years ago
})
}
})
5 years ago
return technologies
}, [])
},
/**
*
* @param {Object} technology
* @param {String} type
* @param {Array} items
*/
5 years ago
analyzeManyToMany(technology, type, items = {}) {
return Object.keys(technology[type]).reduce((technologies, key) => {
const patterns = technology[type][key] || []
const values = items[key] || []
6 years ago
5 years ago
patterns.forEach((pattern) => {
values.forEach((value) => {
if (pattern.regex.test(value)) {
4 years ago
technologies.push({
technology,
pattern,
4 years ago
version: Wappalyzer.resolveVersion(pattern, value),
4 years ago
})
5 years ago
}
})
})
5 years ago
return technologies
}, [])
4 years ago
},
}
5 years ago
if (typeof module !== 'undefined') {
module.exports = Wappalyzer
}