is-localhost-ip 1.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -16
- package/index.js +41 -44
- package/package.json +32 -47
- package/index.js.flow +0 -3
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Comprehensive and robust library to checks whether given host name or IPv4/IPv6 address belongs to the local machine
|
|
6
6
|
|
|
7
|
-
Main difference from other libraries here is comprehensiveness: we start from strict RegExp checks (for IP address first, and then for correctness to
|
|
7
|
+
Main difference from other libraries here is comprehensiveness: we start from strict RegExp checks (for IP address first, and then for correctness to
|
|
8
|
+
be a host name), then fallback to DNS resolver (so it works with something like `john.dev` remapped locally in `hosts` or with local resolver).
|
|
8
9
|
|
|
9
10
|
All this in just _~100 lines of code_ without external dependencies.
|
|
10
11
|
|
|
@@ -19,32 +20,33 @@ yarn add is-localhost-ip
|
|
|
19
20
|
## Example
|
|
20
21
|
|
|
21
22
|
```js
|
|
22
|
-
const isLocalhost = require(
|
|
23
|
+
const isLocalhost = require("is-localhost-ip");
|
|
23
24
|
|
|
24
25
|
(async () => {
|
|
25
|
-
await isLocalhost(
|
|
26
|
-
await isLocalhost(
|
|
27
|
-
await isLocalhost(
|
|
28
|
-
await isLocalhost(
|
|
29
|
-
await isLocalhost(
|
|
30
|
-
|
|
31
|
-
await isLocalhost(
|
|
32
|
-
await isLocalhost(
|
|
33
|
-
await isLocalhost(
|
|
26
|
+
await isLocalhost("127.0.0.1"); // true
|
|
27
|
+
await isLocalhost("::ffff:127.0.0.1"); // true
|
|
28
|
+
await isLocalhost("192.168.0.12"); // true
|
|
29
|
+
await isLocalhost("192.168.0.12", true); // true only if the local machine has an interface with that address
|
|
30
|
+
await isLocalhost("184.55.123.2"); // false
|
|
31
|
+
|
|
32
|
+
await isLocalhost("tino.local"); // true
|
|
33
|
+
await isLocalhost("localhost"); // true
|
|
34
|
+
await isLocalhost("microsoft.com"); // false
|
|
34
35
|
})();
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
## Caveats
|
|
38
39
|
|
|
39
|
-
Doesn't work with internationalized ([RFC 3492](https://tools.ietf.org/html/rfc3492) or [RFC 5891](https://tools.ietf.org/html/rfc5891)) domain names.
|
|
40
|
+
Doesn't work with internationalized ([RFC 3492](https://tools.ietf.org/html/rfc3492) or [RFC 5891](https://tools.ietf.org/html/rfc5891)) domain names.
|
|
41
|
+
If you need that please use wonderful [Punycode.js](https://github.com/bestiejs/punycode.js) to convert the string before passing to this library:
|
|
40
42
|
|
|
41
43
|
```js
|
|
42
|
-
const isLocalhost = require(
|
|
43
|
-
const punycode = require(
|
|
44
|
+
const isLocalhost = require("is-localhost-ip");
|
|
45
|
+
const punycode = require("punycode");
|
|
44
46
|
|
|
45
47
|
(async () => {
|
|
46
|
-
await isLocalhost(punycode.toASCII(
|
|
47
|
-
await isLocalhost(punycode.toASCII(
|
|
48
|
+
await isLocalhost(punycode.toASCII("свобода.рф")); // false
|
|
49
|
+
await isLocalhost(punycode.toASCII("私の.家")); // true
|
|
48
50
|
})();
|
|
49
51
|
```
|
|
50
52
|
|
package/index.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const { isIP, isIPv4 } = require(
|
|
4
|
-
const { createSocket } = require(
|
|
5
|
-
const { ADDRCONFIG } = require(
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
DNS.promises were experimental until Node 11.4 and we don't want experimental warning
|
|
9
|
-
https://nodejs.org/en/blog/release/v11.14.0/
|
|
10
|
-
*/
|
|
11
|
-
const lookup =
|
|
12
|
-
process.versions.node
|
|
13
|
-
.split('.', 2)
|
|
14
|
-
.map((n) => n.padStart(2, '0'))
|
|
15
|
-
.join('.') >= '11.14'
|
|
16
|
-
? // eslint-disable-next-line node/no-unsupported-features/node-builtins
|
|
17
|
-
require('dns').promises.lookup
|
|
18
|
-
: require('util').promisify(require('dns').lookup);
|
|
3
|
+
const { isIP, isIPv4 } = require("node:net");
|
|
4
|
+
const { createSocket } = require("node:dgram");
|
|
5
|
+
const { ADDRCONFIG } = require("node:dns");
|
|
6
|
+
const { lookup } = require("node:dns").promises;
|
|
19
7
|
|
|
20
8
|
/**
|
|
21
9
|
* Addresses reserved for private networks
|
|
@@ -34,22 +22,22 @@ const IP_RANGES = [
|
|
|
34
22
|
// 192.168.0.0 - 192.168.255.255
|
|
35
23
|
/^(:{2}f{4}:)?192\.168(?:\.\d{1,3}){2}$/,
|
|
36
24
|
// fc00::/7
|
|
37
|
-
/^f[cd][\da-f]{2}(::1
|
|
25
|
+
/^f[cd][\da-f]{2}(::1?$|:[\da-f]{1,4}){1,7}$/,
|
|
38
26
|
// fe80::/10
|
|
39
|
-
/^fe[89ab][\da-f](::1
|
|
27
|
+
/^fe[89ab][\da-f](::1?$|:[\da-f]{1,4}){1,7}$/,
|
|
40
28
|
];
|
|
41
29
|
|
|
42
30
|
// Concat all RegExes from above into one
|
|
43
|
-
const IP_TESTER_RE = new RegExp(
|
|
44
|
-
`^(${IP_RANGES.map((re) => re.source).join('|')})$`,
|
|
45
|
-
);
|
|
31
|
+
const IP_TESTER_RE = new RegExp(`^(${IP_RANGES.map((re) => re.source).join("|")})$`);
|
|
46
32
|
|
|
47
33
|
/**
|
|
48
34
|
* Syntax validation RegExp for possible valid host names. Permits underscore.
|
|
49
35
|
* Maximum total length 253 symbols, maximum segment length 63 symbols
|
|
50
36
|
* @see {@link https://en.wikipedia.org/wiki/Hostname}
|
|
51
37
|
*/
|
|
52
|
-
const VALID_HOSTNAME =
|
|
38
|
+
const VALID_HOSTNAME =
|
|
39
|
+
// eslint-disable-next-line regexp/no-dupe-disjunctions
|
|
40
|
+
/(?![\w-]{64})((^(?=[-\w.]{1,253}\.?$)((\w{1,63}|(\w[-\w]{0,61}\w))\.?)+$)(?<!\.{2}))/;
|
|
53
41
|
|
|
54
42
|
/**
|
|
55
43
|
*
|
|
@@ -57,12 +45,12 @@ const VALID_HOSTNAME = /(?![\w-]{64,})((^(?=[\w-.]{1,253}\.?$)((\w{1,63}|(\w[\w-
|
|
|
57
45
|
* @returns {Promise<boolean>}
|
|
58
46
|
*/
|
|
59
47
|
async function canBindToIp(ip) {
|
|
60
|
-
const socket = createSocket(isIPv4(ip) ?
|
|
48
|
+
const socket = createSocket(isIPv4(ip) ? "udp4" : "udp6");
|
|
61
49
|
return new Promise((resolve) => {
|
|
62
50
|
try {
|
|
63
51
|
socket
|
|
64
|
-
.once(
|
|
65
|
-
.once(
|
|
52
|
+
.once("error", () => socket.close(() => resolve(false)))
|
|
53
|
+
.once("listening", () => socket.close(() => resolve(true)))
|
|
66
54
|
.unref()
|
|
67
55
|
.bind(0, ip);
|
|
68
56
|
} catch {
|
|
@@ -79,31 +67,40 @@ async function canBindToIp(ip) {
|
|
|
79
67
|
* @returns {Promise.<boolean>} - true, if given strings is a local IP address or DNS names that resolves to local IP
|
|
80
68
|
*/
|
|
81
69
|
async function isLocalhost(ipOrHostname, canBind = false) {
|
|
82
|
-
if (typeof ipOrHostname !==
|
|
70
|
+
if (typeof ipOrHostname !== "string") {
|
|
71
|
+
throw new TypeError("Invalid ip or hostname provided");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Removes [ and ] around ipv6 hostnames
|
|
75
|
+
const normalizedIpOrHostname = ipOrHostname.replaceAll(/^\[|\]$/g, "");
|
|
83
76
|
|
|
84
77
|
// Check if given string is an IP address
|
|
85
|
-
if (isIP(
|
|
86
|
-
if (IP_TESTER_RE.test(
|
|
87
|
-
return canBindToIp(
|
|
78
|
+
if (isIP(normalizedIpOrHostname)) {
|
|
79
|
+
if (IP_TESTER_RE.test(normalizedIpOrHostname) && !canBind) return true;
|
|
80
|
+
return canBindToIp(normalizedIpOrHostname);
|
|
88
81
|
}
|
|
89
82
|
|
|
90
83
|
// May it be a hostname?
|
|
91
|
-
if (!VALID_HOSTNAME.test(
|
|
84
|
+
if (!VALID_HOSTNAME.test(normalizedIpOrHostname)) {
|
|
85
|
+
throw new Error("Invalid ip or hostname provided");
|
|
86
|
+
}
|
|
92
87
|
|
|
93
88
|
// it's a DNS name
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
89
|
+
const addresses = await lookup(normalizedIpOrHostname, {
|
|
90
|
+
all: true,
|
|
91
|
+
family: 0,
|
|
92
|
+
verbatim: true,
|
|
93
|
+
hints: ADDRCONFIG,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!Array.isArray(addresses)) {
|
|
97
|
+
throw new TypeError("DNS Lookup failed.");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const { address } of addresses) {
|
|
101
|
+
if (await isLocalhost(address, canBind)) return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
107
104
|
return false;
|
|
108
105
|
}
|
|
109
106
|
|
package/package.json
CHANGED
|
@@ -1,61 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "is-localhost-ip",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Checks whether given DNS name or IPv4/IPv6 address belongs to a local machine",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"check-localhost",
|
|
7
|
+
"dns",
|
|
8
|
+
"ip",
|
|
9
|
+
"is-local-ip",
|
|
10
|
+
"is-localhost",
|
|
11
|
+
"is-loopback",
|
|
12
|
+
"localhost"
|
|
10
13
|
],
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"test": "jest --coverage --verbose",
|
|
17
|
-
"lint": "eslint ."
|
|
14
|
+
"homepage": "https://github.com/tinovyatkin/is-localhost-ip#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/tinovyatkin/is-localhost-ip/issues"
|
|
18
17
|
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Konstantin Vyatkin <tino@vtkn.io>",
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
21
22
|
"url": "git+https://github.com/tinovyatkin/is-localhost-ip.git"
|
|
22
23
|
},
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"check-localhost",
|
|
27
|
-
"is-loopback",
|
|
28
|
-
"is-local-ip",
|
|
29
|
-
"ip",
|
|
30
|
-
"dns"
|
|
24
|
+
"files": [
|
|
25
|
+
"index.d.ts",
|
|
26
|
+
"index.js"
|
|
31
27
|
],
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
28
|
+
"main": "index.js",
|
|
29
|
+
"browser": false,
|
|
30
|
+
"types": "index.d.ts",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "node --test --experimental-test-module-mocks --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info __tests__/*.test.js",
|
|
33
|
+
"lint": "oxlint --fix --fix-suggestions",
|
|
34
|
+
"format": "oxfmt --write .",
|
|
35
|
+
"prepare": "lefthook install"
|
|
36
36
|
},
|
|
37
|
-
"homepage": "https://github.com/tinovyatkin/is-localhost-ip#readme",
|
|
38
37
|
"devDependencies": {
|
|
39
|
-
"@types/
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"eslint-plugin-node": "11.1.0",
|
|
44
|
-
"eslint-plugin-optimize-regex": "1.1.7",
|
|
45
|
-
"eslint-plugin-prettier": "^3.1.2",
|
|
46
|
-
"jest": "25.2.7",
|
|
47
|
-
"prettier": "2.0.4",
|
|
48
|
-
"weak-napi": "^1.0.3"
|
|
38
|
+
"@types/node": "22.15.21",
|
|
39
|
+
"lefthook": "^2.0.15",
|
|
40
|
+
"oxfmt": "^0.24.0",
|
|
41
|
+
"oxlint": "^1.39.0"
|
|
49
42
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
"json",
|
|
54
|
-
"cobertura",
|
|
55
|
-
"lcov"
|
|
56
|
-
],
|
|
57
|
-
"testEnvironment": "node",
|
|
58
|
-
"collectCoverage": true
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {}
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
}
|
|
61
46
|
}
|
package/index.js.flow
DELETED