-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 22e3e64
Showing
7 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
.idea | ||
.DS_Store |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "web-whois", | ||
"version": "0.0.1", | ||
"description": "Perform RDAP/WHOIS lookups over HTTP", | ||
"main": "src/index.js", | ||
"keywords": [], | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/MattIPv4/web-whois.git" | ||
}, | ||
"author": "Matt (IPv4) Cowley", | ||
"license": "Apache-2.0", | ||
"bugs": { | ||
"url": "https://github.com/MattIPv4/web-whois/issues" | ||
}, | ||
"homepage": "https://github.com/MattIPv4/web-whois#readme", | ||
"dependencies": { | ||
"node-fetch": "^2.6.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
const rdapLookup = require('./rdap'); | ||
const whoisLookup = require('./whois'); | ||
|
||
module.exports = async (query, combine = false) => { | ||
// Do the RDAP lookup | ||
const rdap = await rdapLookup(query); | ||
|
||
// If we have RDAP data and don't need to combine, return it | ||
if (rdap && !combine) return rdap; | ||
|
||
// Doo the WHOIS lokkup | ||
const whois = await whoisLookup(query); | ||
|
||
// Combine if we can and need to (preferring RDAP) | ||
if (combine && (rdap || whois)) return { ...(whois || {}), ...(rdap || {}) }; | ||
|
||
// Return just the WHOIS data | ||
return whois; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
const fetch = require('node-fetch'); | ||
const { consistentResultObj } = require('./util'); | ||
|
||
// Unique values in an array, comma-separated | ||
const uniqueCommaSep = arr => [...new Set(arr)].join(', '); | ||
|
||
// Find RDAP data entities that match a name | ||
const findEntities = (name, data) => data.entities && data.entities.filter(entity => | ||
entity.roles.map(role => role.trim().toLowerCase()).includes(name)); | ||
|
||
// Find a specific vcard for an RDAP entity | ||
const entityVcard = (entity, vcard) => { | ||
if (entity && entity.vcardArray && Array.isArray(entity.vcardArray) && entity.vcardArray.length > 1) { | ||
const entityFn = entity.vcardArray[1].find(card => card[0] === vcard); | ||
if (entityFn && Array.isArray(entityFn) && entityFn.length > 3) return entityFn[3]; | ||
} | ||
}; | ||
|
||
// Find a vcard name/handle for an RDAP entity | ||
const findEntityName = (name, data) => { | ||
const entities = findEntities(name, data); | ||
if (!entities) return; | ||
|
||
return uniqueCommaSep(entities.map(entity => entityVcard(entity, 'fn') || (entity && entity.handle)) || []); | ||
}; | ||
|
||
// Find a vcard email address for an RDAP entity | ||
const findEntityEmail = (name, data) => { | ||
const entities = findEntities(name, data); | ||
if (!entities) return; | ||
|
||
return uniqueCommaSep(entities.map(entity => entityVcard(entity, 'email')) || []); | ||
}; | ||
|
||
// Get a JS Date for a specific RDAP data event | ||
const findEventDate = (name, data) => { | ||
if (!data.events) return; | ||
const event = data.events.find(event => event.eventAction.trim().toLowerCase() === name); | ||
if (!event || !event.eventDate) return; | ||
return new Date(event.eventDate); | ||
}; | ||
|
||
// Find the ASN information from the RDAP data | ||
const findAsn = data => uniqueCommaSep((data.arin_originas0_originautnums || []).map(asn => asn.toString())); | ||
|
||
// Format RDAP CIDR data | ||
const formatCidr = cidr => cidr && (cidr.v4prefix || cidr.v6prefix) && cidr.length | ||
? (cidr.v4prefix || cidr.v6prefix) + '/' + cidr.length.toString() | ||
: undefined; | ||
|
||
// Find the CIDR blocks from the RDAP data | ||
const findCidr = data => uniqueCommaSep((data.cidr0_cidrs || []).map(formatCidr).filter(cidr => cidr !== undefined)); | ||
|
||
// Find the abuse email in the RDAP data | ||
const findAbuseEmail = data => { | ||
const directAbuse = findEntityEmail('abuse', data); | ||
if (directAbuse) return directAbuse; | ||
|
||
const registrarEntities = findEntities('registrar', data); | ||
if (!registrarEntities) return; | ||
|
||
return findEntityEmail('abuse', { entities: registrarEntities.map(entity => entity.entities).flat(1) }); | ||
}; | ||
|
||
module.exports = async query => { | ||
const resp = await fetch(`https://rdap.cloud/api/v1/${query}`); | ||
const rawData = await resp.json().catch(() => false); | ||
const data = rawData && rawData.results && rawData.results[query]; | ||
|
||
// Ensure the data is there | ||
if (!data || !data.success || !data.data) | ||
return false; | ||
|
||
// Find the useful information for us | ||
const result = consistentResultObj({ | ||
name: data.data.name, | ||
registrant: findEntityName('registrant', data.data), | ||
asn: findAsn(data.data), | ||
registrar: findEntityName('registrar', data.data), | ||
registration: findEventDate('registration', data.data), | ||
expiration: findEventDate('expiration', data.data), | ||
cidr: findCidr(data.data), | ||
abuse: findAbuseEmail(data.data), | ||
}); | ||
|
||
// Return false if we found nothing, otherwise return the data | ||
return Object.values(result).every(x => x === undefined) ? false : result; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module.exports.consistentResultObj = data => ({ | ||
name: data.name || undefined, | ||
registrant: data.registrant || undefined, | ||
asn: data.asn || undefined, | ||
registrar: data.registrar || undefined, | ||
registration: data.registration || undefined, | ||
expiration: data.expiration || undefined, | ||
cidr: data.cidr || undefined, | ||
abuse: data.abuse || undefined, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
const fetch = require('node-fetch'); | ||
const { consistentResultObj } = require('./util'); | ||
|
||
const parseWhois = text => { | ||
// RegExp parts | ||
const reLinebreak = '\\r\\n'; | ||
const reWhitespace = `[^\\S${reLinebreak}]`; | ||
const reKey = '([a-zA-Z\\s]+):'; | ||
const reText = `([^${reLinebreak}]+)`; | ||
const reLineStart = `^${reWhitespace}*${reKey}`; | ||
const reLineEnd = `${reWhitespace}+${reText}$`; | ||
|
||
// The RegExps to be used | ||
const reSingleLine = `${reLineStart}${reLineEnd}`; | ||
const regExpSingleLineGm = new RegExp(reSingleLine, 'gm'); | ||
const regExpSingleLine = new RegExp(reSingleLine); | ||
const reSplitLine = `${reLineStart}[${reLinebreak}]+${reLineEnd}`; | ||
const regExpSplitLineGm = new RegExp(reSplitLine, 'gm'); | ||
const regExpSplitLine = new RegExp(reSplitLine); | ||
|
||
// Find the matches in the string | ||
const singleLineMatches = text.match(regExpSingleLineGm) || []; | ||
const splitLineMatches = text.match(regExpSplitLineGm) || []; | ||
const matches = []; | ||
|
||
// All single line matches are valid | ||
for (const rawMatch of singleLineMatches) { | ||
const match = rawMatch.trim().match(regExpSingleLine); | ||
matches.push({ | ||
key: match[1], | ||
value: match[2], | ||
}); | ||
} | ||
|
||
// Split line matches that don't include a single line match are valid | ||
for (const rawMatch of splitLineMatches) { | ||
if (singleLineMatches.map(singleLineMatch => rawMatch.includes(singleLineMatch)).includes(true)) | ||
continue; | ||
|
||
const match = rawMatch.trim().match(regExpSplitLine); | ||
matches.push({ | ||
key: match[1], | ||
value: match[2], | ||
}); | ||
} | ||
|
||
// Return the final parsed data | ||
return matches; | ||
}; | ||
|
||
// Find an attribute value from the WHOIS data | ||
const findAttribute = (name, data) => { | ||
const entry = data.find(entry => entry.key.trim().toLowerCase() === name); | ||
return entry && entry.value && `${entry.value}`.trim(); | ||
}; | ||
|
||
// Find a JS Date for an attribute from the WHOIS data | ||
const findAttributeDate = (name, data) => { | ||
const attribute = findAttribute(name, data); | ||
if (!attribute) return; | ||
return new Date(attribute); | ||
}; | ||
|
||
module.exports = async query => { | ||
const resp = await fetch(`https://whoisjs.com/api/v1/${query}`); | ||
const rawData = await resp.json().catch(() => false); | ||
|
||
// Ensure the data is there | ||
if (!rawData || !rawData.success || !rawData.raw) | ||
return false; | ||
|
||
// Parse ourselves | ||
const data = parseWhois(rawData.raw); | ||
if (!data) | ||
return false; | ||
|
||
// Find the useful information for us | ||
const result = consistentResultObj({ | ||
registrant: findAttribute('registrant', data), | ||
registrar: findAttribute('registrar', data) || findAttribute('organisation', data), | ||
registration: findAttributeDate('creation date', data) || findAttributeDate('registered on', data), | ||
expiration: findAttributeDate('registry expiry date', data) || findAttributeDate('expiry date', data), | ||
abuse: findAttribute('registrar abuse contact email', data), | ||
}); | ||
|
||
// Return false if we found nothing, otherwise return the data | ||
return Object.values(result).every(x => x === undefined) ? false : result; | ||
}; |