is-localhost-ip 1.2.0 → 2.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 CHANGED
@@ -8,7 +8,6 @@ Main difference from other libraries here is comprehensiveness: we start from st
8
8
 
9
9
  All this in just _~100 lines of code_ without external dependencies.
10
10
 
11
-
12
11
  ## Installation
13
12
 
14
13
  ```sh
@@ -23,18 +22,16 @@ yarn add is-localhost-ip
23
22
  const isLocalhost = require('is-localhost-ip');
24
23
 
25
24
  (async () => {
26
-
27
- await isLocalhost("127.0.0.1"); // true
28
- await isLocalhost("::ffff:127.0.0.1"); // true
29
- await isLocalhost("192.168.0.12"); // true
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
35
-
25
+ await isLocalhost('127.0.0.1'); // true
26
+ await isLocalhost('::ffff:127.0.0.1'); // true
27
+ await isLocalhost('192.168.0.12'); // true
28
+ await isLocalhost('192.168.0.12', true); // true only if the local machine has an interface with that address
29
+ await isLocalhost('184.55.123.2'); // false
30
+
31
+ await isLocalhost('tino.local'); // true
32
+ await isLocalhost('localhost'); // true
33
+ await isLocalhost('microsoft.com'); // false
36
34
  })();
37
-
38
35
  ```
39
36
 
40
37
  ## Caveats
@@ -46,8 +43,8 @@ const isLocalhost = require('is-localhost-ip');
46
43
  const punycode = require('punycode');
47
44
 
48
45
  (async () => {
49
- await isLocalhost(punycode.toASCII("кремль.рф")); // false
50
- await isLocalhost(punycode.toASCII("私の.家")); // true
46
+ await isLocalhost(punycode.toASCII('свобода.рф')); // false
47
+ await isLocalhost(punycode.toASCII('私の.家')); // true
51
48
  })();
52
49
  ```
53
50
 
package/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  /** Returns true if given parameter is local IP address or resolves into one, false otherwise */
2
- declare async function isLocalhost(addrOrHost: string): Promise<boolean>;
2
+ declare async function isLocalhost(
3
+ addrOrHost: string,
4
+ canBind?: boolean,
5
+ ): Promise<boolean>;
3
6
 
4
7
  export = isLocalhost;
package/index.js CHANGED
@@ -1,109 +1,100 @@
1
1
  'use strict';
2
2
 
3
- const { isIP } = require('net');
4
- const { networkInterfaces } = require('os');
5
-
6
- /*
7
- DNS.promises were experimental until Node 11.4 and we don't want experimental warning
8
- https://nodejs.org/en/blog/release/v11.14.0/
9
- */
10
- const lookup =
11
- process.versions.node
12
- .split('.', 2)
13
- .map(n => n.padStart(2, '0'))
14
- .join('.') >= '11.14'
15
- ? // eslint-disable-next-line node/no-unsupported-features/node-builtins
16
- require('dns').promises.lookup
17
- : require('util').promisify(require('dns').lookup);
18
-
19
- const LOCAL_INTERFACES = networkInterfaces();
20
- const INTERFACES_ADDRESSES = /** @type {Set<string>} */ (new Set());
21
-
22
- /*
23
- We will check if every network interface has an IPv4 or IPv6 address
24
- to try to avoid lookup both families
25
- */
26
- let haveIPv4 = 0;
27
- let haveIPv6 = 0;
28
- for (const interfaceInfo of Object.values(LOCAL_INTERFACES)) {
29
- let v4 = false;
30
- let v6 = false;
31
- for (const { address, family } of interfaceInfo) {
32
- INTERFACES_ADDRESSES.add(address);
33
- if (family === 'IPv4') v4 = true;
34
- else v6 = true;
35
- }
36
- if (v4) haveIPv4++;
37
- if (v6) haveIPv6++;
38
- }
39
-
40
- const totalInterfaces = Object.keys(LOCAL_INTERFACES).length;
41
- const LOOKUP_OPTIONS = /** @type {import('dns').LookupAllOptions} */ ({
42
- all: true,
43
- family:
44
- totalInterfaces === haveIPv4 ? 4 : totalInterfaces === haveIPv6 ? 6 : 0,
45
- });
3
+ const { isIP, isIPv4 } = require('net');
4
+ const { createSocket } = require('dgram');
5
+ const { ADDRCONFIG } = require('dns');
6
+ const { lookup } = require('dns').promises;
46
7
 
8
+ /**
9
+ * Addresses reserved for private networks
10
+ * @see {@link https://en.wikipedia.org/wiki/Private_network}
11
+ * @see {@link https://en.wikipedia.org/wiki/Unique_local_address}
12
+ */
47
13
  const IP_RANGES = [
48
14
  // 10.0.0.0 - 10.255.255.255
49
- /^(::f{4}:)?10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
15
+ /^(:{2}f{4}:)?10(?:\.\d{1,3}){3}$/,
50
16
  // 127.0.0.0 - 127.255.255.255
51
- /^(::f{4}:)?127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
17
+ /^(:{2}f{4}:)?127(?:\.\d{1,3}){3}$/,
52
18
  // 169.254.1.0 - 169.254.254.255
53
19
  /^(::f{4}:)?169\.254\.([1-9]|1?\d\d|2[0-4]\d|25[0-4])\.\d{1,3}$/,
54
20
  // 172.16.0.0 - 172.31.255.255
55
- /^(::f{4}:)?(172\.1[6-9]|172\.2\d|172\.3[0-1])\.\d{1,3}\.\d{1,3}$/,
21
+ /^(:{2}f{4}:)?(172\.1[6-9]|172\.2\d|172\.3[01])(?:\.\d{1,3}){2}$/,
56
22
  // 192.168.0.0 - 192.168.255.255
57
- /^(::f{4}:)?192\.168\.\d{1,3}\.\d{1,3}$/,
23
+ /^(:{2}f{4}:)?192\.168(?:\.\d{1,3}){2}$/,
58
24
  // fc00::/7
59
- /^f[c-d][0-9a-f]{2}(::1$|:[0-9a-f]{1,4}){1,7}$/,
25
+ /^f[cd][\da-f]{2}(::1$|:[\da-f]{1,4}){1,7}$/,
60
26
  // fe80::/10
61
- /^fe[89ab][0-9a-f](::1$|:[0-9a-f]{1,4}){1,7}$/,
27
+ /^fe[89ab][\da-f](::1$|:[\da-f]{1,4}){1,7}$/,
62
28
  ];
63
29
 
30
+ // Concat all RegExes from above into one
31
+ const IP_TESTER_RE = new RegExp(
32
+ `^(${IP_RANGES.map((re) => re.source).join('|')})$`,
33
+ );
34
+
64
35
  /**
65
36
  * Syntax validation RegExp for possible valid host names. Permits underscore.
66
37
  * Maximum total length 253 symbols, maximum segment length 63 symbols
67
38
  * @see {@link https://en.wikipedia.org/wiki/Hostname}
68
39
  */
69
- const VALID_HOSTNAME = /(?![a-z0-9-_]{64,})((^(?=[-a-z0-9._]{1,253}\.?$)(([a-z0-9_]{1,63}|([a-z0-9_][a-z0-9-_]{0,61}[a-z0-9_]))\.?)+$)(?<!\.{2,}))/i;
40
+ const VALID_HOSTNAME =
41
+ // eslint-disable-next-line regexp/no-dupe-disjunctions
42
+ /(?![\w-]{64})((^(?=[-\w.]{1,253}\.?$)((\w{1,63}|(\w[-\w]{0,61}\w))\.?)+$)(?<!\.{2}))/;
70
43
 
71
44
  /**
72
- * Checks if given strings is a local IP address or a DNS name that resolve into a local IP
73
45
  *
74
46
  * @param {string} ip
47
+ * @returns {Promise<boolean>}
48
+ */
49
+ async function canBindToIp(ip) {
50
+ const socket = createSocket(isIPv4(ip) ? 'udp4' : 'udp6');
51
+ return new Promise((resolve) => {
52
+ try {
53
+ socket
54
+ .once('error', () => socket.close(() => resolve(false)))
55
+ .once('listening', () => socket.close(() => resolve(true)))
56
+ .unref()
57
+ .bind(0, ip);
58
+ } catch {
59
+ socket.close(() => resolve(false));
60
+ }
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Checks if given strings is a local IP address or a DNS name that resolve into a local IP
66
+ *
67
+ * @param {string} ipOrHostname
68
+ * @param {boolean} [canBind=false] - should check whether an interface with such address exists on the local machine
75
69
  * @returns {Promise.<boolean>} - true, if given strings is a local IP address or DNS names that resolves to local IP
76
70
  */
77
- async function isLocalhost(ip) {
78
- if (typeof ip !== 'string') return false;
71
+ async function isLocalhost(ipOrHostname, canBind = false) {
72
+ if (typeof ipOrHostname !== 'string') return false;
79
73
 
80
74
  // Check if given string is an IP address
81
- if (isIP(ip)) {
82
- return (
83
- ip === '::' ||
84
- ip === '::1' ||
85
- IP_RANGES.some(it => it.test(ip)) ||
86
- INTERFACES_ADDRESSES.has(ip)
87
- );
75
+ if (isIP(ipOrHostname)) {
76
+ if (IP_TESTER_RE.test(ipOrHostname) && !canBind) return true;
77
+ return canBindToIp(ipOrHostname);
88
78
  }
89
79
 
90
80
  // May it be a hostname?
91
- if (!VALID_HOSTNAME.test(ip)) return false;
81
+ if (!VALID_HOSTNAME.test(ipOrHostname)) return false;
92
82
 
93
83
  // it's a DNS name
94
84
  try {
95
- const addresses = await lookup(ip, LOOKUP_OPTIONS);
96
- return (
97
- Array.isArray(addresses) &&
98
- addresses.some(
99
- ({ address }) =>
100
- IP_RANGES.some(it => it.test(address)) ||
101
- INTERFACES_ADDRESSES.has(address),
102
- )
103
- );
104
- } catch (_) {
105
- return false;
106
- }
85
+ const addresses = await lookup(ipOrHostname, {
86
+ all: true,
87
+ family: 0,
88
+ verbatim: true,
89
+ hints: ADDRCONFIG,
90
+ });
91
+ if (!Array.isArray(addresses)) return false;
92
+ for (const { address } of addresses) {
93
+ if (await isLocalhost(address, canBind)) return true;
94
+ }
95
+ // eslint-disable-next-line no-empty
96
+ } catch {}
97
+ return false;
107
98
  }
108
99
 
109
100
  module.exports = isLocalhost;
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "is-localhost-ip",
3
- "version": "1.2.0",
4
- "description": "Checks whether given DNS name or IPv4/IPv6 address belongs to local machine",
3
+ "version": "2.0.0",
4
+ "description": "Checks whether given DNS name or IPv4/IPv6 address belongs to a local machine",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
8
- "index.d.ts",
9
- "index.js.flow"
8
+ "index.d.ts"
10
9
  ],
11
10
  "types": "index.d.ts",
12
11
  "engines": {
13
- "node": ">=10"
12
+ "node": ">=12"
14
13
  },
15
14
  "scripts": {
16
- "test": "jest --coverage --verbose"
15
+ "test": "jest --coverage --verbose",
16
+ "lint": "eslint ."
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
@@ -29,19 +29,21 @@
29
29
  "dns"
30
30
  ],
31
31
  "author": "Konstantin Vyatkin <tino@vtkn.io>",
32
- "license": "GPL-3.0",
32
+ "license": "MIT",
33
33
  "bugs": {
34
34
  "url": "https://github.com/tinovyatkin/is-localhost-ip/issues"
35
35
  },
36
36
  "homepage": "https://github.com/tinovyatkin/is-localhost-ip#readme",
37
37
  "devDependencies": {
38
- "@types/jest": "24.0.18",
39
- "@types/node": "12.7.2",
40
- "eslint-plugin-node": "9.1.0",
41
- "eslint": "6.2.2",
42
- "jest": "24.9.0",
43
- "jest-junit": "7.0.0",
44
- "prettier": "1.18.2"
38
+ "@types/jest": "28.1.6",
39
+ "@types/node": "~12",
40
+ "eslint": "8.21.0",
41
+ "eslint-config-prettier": "8.5.0",
42
+ "eslint-plugin-node": "11.1.0",
43
+ "eslint-plugin-prettier": "4.2.1",
44
+ "eslint-plugin-regexp": "^1.8.0",
45
+ "jest": "28.1.3",
46
+ "prettier": "2.7.1"
45
47
  },
46
48
  "jest": {
47
49
  "coverageReporters": [
@@ -50,8 +52,8 @@
50
52
  "cobertura",
51
53
  "lcov"
52
54
  ],
55
+ "coverageProvider": "v8",
53
56
  "testEnvironment": "node",
54
57
  "collectCoverage": true
55
- },
56
- "dependencies": {}
58
+ }
57
59
  }
package/index.js.flow DELETED
@@ -1,3 +0,0 @@
1
- // @flow
2
-
3
- declare module.exports: (addrOrHost: string) => Promise.<Boolean>;