Files
pole-book/server/node_modules/is-localhost-ip/index.js

102 lines
3.1 KiB
JavaScript

'use strict';
const { isIP, isIPv4 } = require('net');
const { createSocket } = require('dgram');
const { ADDRCONFIG } = require('dns');
const { lookup } = require('dns').promises;
/**
* Addresses reserved for private networks
* @see {@link https://en.wikipedia.org/wiki/Private_network}
* @see {@link https://en.wikipedia.org/wiki/Unique_local_address}
*/
const IP_RANGES = [
// 10.0.0.0 - 10.255.255.255
/^(:{2}f{4}:)?10(?:\.\d{1,3}){3}$/,
// 127.0.0.0 - 127.255.255.255
/^(:{2}f{4}:)?127(?:\.\d{1,3}){3}$/,
// 169.254.1.0 - 169.254.254.255
/^(::f{4}:)?169\.254\.([1-9]|1?\d\d|2[0-4]\d|25[0-4])\.\d{1,3}$/,
// 172.16.0.0 - 172.31.255.255
/^(:{2}f{4}:)?(172\.1[6-9]|172\.2\d|172\.3[01])(?:\.\d{1,3}){2}$/,
// 192.168.0.0 - 192.168.255.255
/^(:{2}f{4}:)?192\.168(?:\.\d{1,3}){2}$/,
// fc00::/7
/^f[cd][\da-f]{2}(::1$|:[\da-f]{1,4}){1,7}$/,
// fe80::/10
/^fe[89ab][\da-f](::1$|:[\da-f]{1,4}){1,7}$/,
];
// Concat all RegExes from above into one
const IP_TESTER_RE = new RegExp(
`^(${IP_RANGES.map((re) => re.source).join('|')})$`,
);
/**
* Syntax validation RegExp for possible valid host names. Permits underscore.
* Maximum total length 253 symbols, maximum segment length 63 symbols
* @see {@link https://en.wikipedia.org/wiki/Hostname}
*/
const VALID_HOSTNAME =
// eslint-disable-next-line regexp/no-dupe-disjunctions
/(?![\w-]{64})((^(?=[-\w.]{1,253}\.?$)((\w{1,63}|(\w[-\w]{0,61}\w))\.?)+$)(?<!\.{2}))/;
/**
*
* @param {string} ip
* @returns {Promise<boolean>}
*/
async function canBindToIp(ip) {
const socket = createSocket(isIPv4(ip) ? 'udp4' : 'udp6');
return new Promise((resolve) => {
try {
socket
.once('error', () => socket.close(() => resolve(false)))
.once('listening', () => socket.close(() => resolve(true)))
.unref()
.bind(0, ip);
} catch {
socket.close(() => resolve(false));
}
});
}
/**
* Checks if given strings is a local IP address or a DNS name that resolve into a local IP
*
* @param {string} ipOrHostname
* @param {boolean} [canBind=false] - should check whether an interface with such address exists on the local machine
* @returns {Promise.<boolean>} - true, if given strings is a local IP address or DNS names that resolves to local IP
*/
async function isLocalhost(ipOrHostname, canBind = false) {
if (typeof ipOrHostname !== 'string') return false;
// Check if given string is an IP address
if (isIP(ipOrHostname)) {
if (IP_TESTER_RE.test(ipOrHostname) && !canBind) return true;
return canBindToIp(ipOrHostname);
}
// May it be a hostname?
if (!VALID_HOSTNAME.test(ipOrHostname)) return false;
// it's a DNS name
try {
const addresses = await lookup(ipOrHostname, {
all: true,
family: 0,
verbatim: true,
hints: ADDRCONFIG,
});
if (!Array.isArray(addresses)) return false;
for (const { address } of addresses) {
if (await isLocalhost(address, canBind)) return true;
}
// eslint-disable-next-line no-empty
} catch {}
return false;
}
module.exports = isLocalhost;
module.exports.VALID_HOSTNAME = VALID_HOSTNAME; // for tests