-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
101 lines (91 loc) · 3.08 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
'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