@zhin.js/plugin-nettools 0.1.1 → 0.1.3
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/CHANGELOG.md +20 -0
- package/commands/dns/[name]/[[type]]/index.js +5 -5
- package/commands/dns/[name]/[[type]]/index.ts +5 -5
- package/commands/domain-price/[suffix]/[[order]]/index.js +5 -5
- package/commands/domain-price/[suffix]/[[order]]/index.ts +5 -5
- package/commands/ping/[host]/index.js +4 -4
- package/commands/ping/[host]/index.ts +4 -4
- package/commands/whois/[domain]/index.js +4 -4
- package/commands/whois/[domain]/index.ts +4 -4
- package/lib/address-policy.d.ts +2 -0
- package/lib/address-policy.js +55 -54
- package/lib/address-policy.js.map +1 -1
- package/lib/cooldown.js +2 -1
- package/lib/cooldown.js.map +1 -1
- package/lib/dns.d.ts +2 -1
- package/lib/dns.js +14 -12
- package/lib/dns.js.map +1 -1
- package/lib/domain-price.d.ts +2 -1
- package/lib/domain-price.js +16 -19
- package/lib/domain-price.js.map +1 -1
- package/lib/errors.d.ts +5 -2
- package/lib/errors.js +8 -4
- package/lib/errors.js.map +1 -1
- package/lib/http.js +12 -11
- package/lib/http.js.map +1 -1
- package/lib/messages.d.ts +329 -0
- package/lib/messages.js +335 -0
- package/lib/messages.js.map +1 -0
- package/lib/ping.d.ts +2 -1
- package/lib/ping.js +13 -11
- package/lib/ping.js.map +1 -1
- package/lib/presentation.d.ts +1 -0
- package/lib/presentation.js +36 -0
- package/lib/presentation.js.map +1 -0
- package/lib/service.d.ts +6 -5
- package/lib/service.js +16 -15
- package/lib/service.js.map +1 -1
- package/lib/target-guard.js +6 -4
- package/lib/target-guard.js.map +1 -1
- package/lib/whois.d.ts +2 -1
- package/lib/whois.js +18 -16
- package/lib/whois.js.map +1 -1
- package/package.json +7 -7
- package/skills/nettools/tools/dns_lookup/index.js +1 -1
- package/skills/nettools/tools/domain_price/index.js +1 -1
- package/skills/nettools/tools/net_ping/index.js +1 -1
- package/skills/nettools/tools/whois_lookup/index.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @zhin.js/plugin-nettools
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 53bc6a0: 官方工具插件与游戏支持中英文反馈、帮助和配置标签;提醒增加与中文对应的有限英文时间语法。聊天后台任务记录创建者当时语言,管理员任务记录项目语言,旧任务保持中文;多轮选择与游戏使用开始时语言,保留第三方内容和已生成报告。
|
|
8
|
+
- Updated dependencies [53bc6a0]
|
|
9
|
+
- Updated dependencies [7595136]
|
|
10
|
+
- Updated dependencies [17d1301]
|
|
11
|
+
- @zhin.js/tool@1.1.10
|
|
12
|
+
- @zhin.js/skill@1.1.10
|
|
13
|
+
|
|
14
|
+
## 0.1.2
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [6da2bad]
|
|
19
|
+
- Updated dependencies [3781f1c]
|
|
20
|
+
- @zhin.js/tool@1.1.9
|
|
21
|
+
- @zhin.js/skill@1.1.9
|
|
22
|
+
|
|
3
23
|
## 0.1.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -3,16 +3,16 @@ import { defineCommand } from 'zhin.js/command';
|
|
|
3
3
|
import { commandCallerKey } from "../../../../lib/cooldown.js";
|
|
4
4
|
import { nettoolsServiceToken } from "../../../../lib/runtime.js";
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: 'DNS 查询(DoH),类型可选 A/AAAA/CNAME/MX/TXT/NS/SOA/CAA/SRV/PTR/DS/DNSKEY',
|
|
6
|
+
description: 'DNS 查询(DoH),类型可选 A/AAAA/CNAME/MX/TXT/NS/SOA/CAA/SRV/PTR/DS/DNSKEY', descriptionI18n: { en: "DNS lookup over HTTPS; supports A/AAAA/CNAME/MX/TXT/NS/SOA/CAA/SRV/PTR/DS/DNSKEY" },
|
|
7
7
|
params: {
|
|
8
|
-
name: { type: 'string', description: '域名;PTR 查询时可填 IP' },
|
|
9
|
-
type: { type: 'string', description: '记录类型,默认 A' },
|
|
8
|
+
name: { type: 'string', description: '域名;PTR 查询时可填 IP', descriptionI18n: { en: "Domain name, or an IP address for PTR queries" } },
|
|
9
|
+
type: { type: 'string', description: '记录类型,默认 A', descriptionI18n: { en: "Record type (default: A)" } },
|
|
10
10
|
},
|
|
11
|
-
async execute({ params, input, use, signal }) {
|
|
11
|
+
async execute({ params, input, use, signal, locale }) {
|
|
12
12
|
const service = use(nettoolsServiceToken);
|
|
13
13
|
const name = String(params.name ?? '');
|
|
14
14
|
const type = params.type == null ? undefined : String(params.type);
|
|
15
15
|
const callerKey = commandCallerKey(input);
|
|
16
|
-
return service.run(() => service.dns(name, type, callerKey, signal));
|
|
16
|
+
return service.run(() => service.dns(name, type, callerKey, signal, locale), locale);
|
|
17
17
|
},
|
|
18
18
|
});
|
|
@@ -3,16 +3,16 @@ import { commandCallerKey } from '../../../../src/cooldown.js';
|
|
|
3
3
|
import { nettoolsServiceToken } from '../../../../src/runtime.js';
|
|
4
4
|
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: 'DNS 查询(DoH),类型可选 A/AAAA/CNAME/MX/TXT/NS/SOA/CAA/SRV/PTR/DS/DNSKEY',
|
|
6
|
+
description: 'DNS 查询(DoH),类型可选 A/AAAA/CNAME/MX/TXT/NS/SOA/CAA/SRV/PTR/DS/DNSKEY', descriptionI18n: { en: "DNS lookup over HTTPS; supports A/AAAA/CNAME/MX/TXT/NS/SOA/CAA/SRV/PTR/DS/DNSKEY" },
|
|
7
7
|
params: {
|
|
8
|
-
name: { type: 'string', description: '域名;PTR 查询时可填 IP' },
|
|
9
|
-
type: { type: 'string', description: '记录类型,默认 A' },
|
|
8
|
+
name: { type: 'string', description: '域名;PTR 查询时可填 IP', descriptionI18n: { en: "Domain name, or an IP address for PTR queries" } },
|
|
9
|
+
type: { type: 'string', description: '记录类型,默认 A', descriptionI18n: { en: "Record type (default: A)" } },
|
|
10
10
|
},
|
|
11
|
-
async execute({ params, input, use, signal }) {
|
|
11
|
+
async execute({ params, input, use, signal, locale }) {
|
|
12
12
|
const service = use(nettoolsServiceToken);
|
|
13
13
|
const name = String(params.name ?? '');
|
|
14
14
|
const type = params.type == null ? undefined : String(params.type);
|
|
15
15
|
const callerKey = commandCallerKey(input);
|
|
16
|
-
return service.run(() => service.dns(name, type, callerKey, signal));
|
|
16
|
+
return service.run(() => service.dns(name, type, callerKey, signal, locale), locale);
|
|
17
17
|
},
|
|
18
18
|
});
|
|
@@ -3,17 +3,17 @@ import { defineCommand } from 'zhin.js/command';
|
|
|
3
3
|
import { commandCallerKey } from "../../../../lib/cooldown.js";
|
|
4
4
|
import { nettoolsServiceToken } from "../../../../lib/runtime.js";
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '域名注册/续费/转入价格比价(nazhumi.com)',
|
|
6
|
+
description: '域名注册/续费/转入价格比价(nazhumi.com)', descriptionI18n: { en: "Compare domain registration, renewal and transfer prices (nazhumi.com)" },
|
|
7
7
|
alias: ['域名比价'],
|
|
8
8
|
params: {
|
|
9
|
-
suffix: { type: 'string', description: '域名后缀(com、.cn、.co.uk)或完整域名' },
|
|
10
|
-
order: { type: 'string', description: '注册(register) / 续费(renew) / 转入(transfer),默认注册' },
|
|
9
|
+
suffix: { type: 'string', description: '域名后缀(com、.cn、.co.uk)或完整域名', descriptionI18n: { en: "Domain suffix (com, .cn, .co.uk) or a full domain" } },
|
|
10
|
+
order: { type: 'string', description: '注册(register) / 续费(renew) / 转入(transfer),默认注册', descriptionI18n: { en: "register (default), renew, or transfer" } },
|
|
11
11
|
},
|
|
12
|
-
async execute({ params, input, use, signal }) {
|
|
12
|
+
async execute({ params, input, use, signal, locale }) {
|
|
13
13
|
const service = use(nettoolsServiceToken);
|
|
14
14
|
const suffix = String(params.suffix ?? '');
|
|
15
15
|
const order = params.order == null ? undefined : String(params.order);
|
|
16
16
|
const callerKey = commandCallerKey(input);
|
|
17
|
-
return service.run(() => service.domainPrice(suffix, order, callerKey, signal));
|
|
17
|
+
return service.run(() => service.domainPrice(suffix, order, callerKey, signal, locale), locale);
|
|
18
18
|
},
|
|
19
19
|
});
|
|
@@ -3,17 +3,17 @@ import { commandCallerKey } from '../../../../src/cooldown.js';
|
|
|
3
3
|
import { nettoolsServiceToken } from '../../../../src/runtime.js';
|
|
4
4
|
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '域名注册/续费/转入价格比价(nazhumi.com)',
|
|
6
|
+
description: '域名注册/续费/转入价格比价(nazhumi.com)', descriptionI18n: { en: "Compare domain registration, renewal and transfer prices (nazhumi.com)" },
|
|
7
7
|
alias: ['域名比价'],
|
|
8
8
|
params: {
|
|
9
|
-
suffix: { type: 'string', description: '域名后缀(com、.cn、.co.uk)或完整域名' },
|
|
10
|
-
order: { type: 'string', description: '注册(register) / 续费(renew) / 转入(transfer),默认注册' },
|
|
9
|
+
suffix: { type: 'string', description: '域名后缀(com、.cn、.co.uk)或完整域名', descriptionI18n: { en: "Domain suffix (com, .cn, .co.uk) or a full domain" } },
|
|
10
|
+
order: { type: 'string', description: '注册(register) / 续费(renew) / 转入(transfer),默认注册', descriptionI18n: { en: "register (default), renew, or transfer" } },
|
|
11
11
|
},
|
|
12
|
-
async execute({ params, input, use, signal }) {
|
|
12
|
+
async execute({ params, input, use, signal, locale }) {
|
|
13
13
|
const service = use(nettoolsServiceToken);
|
|
14
14
|
const suffix = String(params.suffix ?? '');
|
|
15
15
|
const order = params.order == null ? undefined : String(params.order);
|
|
16
16
|
const callerKey = commandCallerKey(input);
|
|
17
|
-
return service.run(() => service.domainPrice(suffix, order, callerKey, signal));
|
|
17
|
+
return service.run(() => service.domainPrice(suffix, order, callerKey, signal, locale), locale);
|
|
18
18
|
},
|
|
19
19
|
});
|
|
@@ -3,12 +3,12 @@ import { defineCommand } from 'zhin.js/command';
|
|
|
3
3
|
import { commandCallerKey } from "../../../lib/cooldown.js";
|
|
4
4
|
import { nettoolsServiceToken } from "../../../lib/runtime.js";
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '从多个地区 Ping 一个公网主机(check-host.net)',
|
|
7
|
-
params: { host: { type: 'string', description: '域名或公网 IP' } },
|
|
8
|
-
async execute({ params, input, use, signal }) {
|
|
6
|
+
description: '从多个地区 Ping 一个公网主机(check-host.net)', descriptionI18n: { en: "Ping a public host from multiple regions (check-host.net)" },
|
|
7
|
+
params: { host: { type: 'string', description: '域名或公网 IP', descriptionI18n: { en: "Domain name or public IP address" } } },
|
|
8
|
+
async execute({ params, input, use, signal, locale }) {
|
|
9
9
|
const service = use(nettoolsServiceToken);
|
|
10
10
|
const host = String(params.host ?? '');
|
|
11
11
|
const callerKey = commandCallerKey(input);
|
|
12
|
-
return service.run(() => service.ping(host, callerKey, signal));
|
|
12
|
+
return service.run(() => service.ping(host, callerKey, signal, locale), locale);
|
|
13
13
|
},
|
|
14
14
|
});
|
|
@@ -3,12 +3,12 @@ import { commandCallerKey } from '../../../src/cooldown.js';
|
|
|
3
3
|
import { nettoolsServiceToken } from '../../../src/runtime.js';
|
|
4
4
|
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '从多个地区 Ping 一个公网主机(check-host.net)',
|
|
7
|
-
params: { host: { type: 'string', description: '域名或公网 IP' } },
|
|
8
|
-
async execute({ params, input, use, signal }) {
|
|
6
|
+
description: '从多个地区 Ping 一个公网主机(check-host.net)', descriptionI18n: { en: "Ping a public host from multiple regions (check-host.net)" },
|
|
7
|
+
params: { host: { type: 'string', description: '域名或公网 IP', descriptionI18n: { en: "Domain name or public IP address" } } },
|
|
8
|
+
async execute({ params, input, use, signal, locale }) {
|
|
9
9
|
const service = use(nettoolsServiceToken);
|
|
10
10
|
const host = String(params.host ?? '');
|
|
11
11
|
const callerKey = commandCallerKey(input);
|
|
12
|
-
return service.run(() => service.ping(host, callerKey, signal));
|
|
12
|
+
return service.run(() => service.ping(host, callerKey, signal, locale), locale);
|
|
13
13
|
},
|
|
14
14
|
});
|
|
@@ -3,12 +3,12 @@ import { defineCommand } from 'zhin.js/command';
|
|
|
3
3
|
import { commandCallerKey } from "../../../lib/cooldown.js";
|
|
4
4
|
import { nettoolsServiceToken } from "../../../lib/runtime.js";
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '查询域名注册信息(RDAP / WHOIS)',
|
|
7
|
-
params: { domain: { type: 'string', description: '要查询的域名' } },
|
|
8
|
-
async execute({ params, input, use, signal }) {
|
|
6
|
+
description: '查询域名注册信息(RDAP / WHOIS)', descriptionI18n: { en: "Look up domain registration information using RDAP / WHOIS" },
|
|
7
|
+
params: { domain: { type: 'string', description: '要查询的域名', descriptionI18n: { en: "Domain to look up" } } },
|
|
8
|
+
async execute({ params, input, use, signal, locale }) {
|
|
9
9
|
const service = use(nettoolsServiceToken);
|
|
10
10
|
const domain = String(params.domain ?? '');
|
|
11
11
|
const callerKey = commandCallerKey(input);
|
|
12
|
-
return service.run(() => service.whois(domain, callerKey, signal));
|
|
12
|
+
return service.run(() => service.whois(domain, callerKey, signal, locale), locale);
|
|
13
13
|
},
|
|
14
14
|
});
|
|
@@ -3,12 +3,12 @@ import { commandCallerKey } from '../../../src/cooldown.js';
|
|
|
3
3
|
import { nettoolsServiceToken } from '../../../src/runtime.js';
|
|
4
4
|
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '查询域名注册信息(RDAP / WHOIS)',
|
|
7
|
-
params: { domain: { type: 'string', description: '要查询的域名' } },
|
|
8
|
-
async execute({ params, input, use, signal }) {
|
|
6
|
+
description: '查询域名注册信息(RDAP / WHOIS)', descriptionI18n: { en: "Look up domain registration information using RDAP / WHOIS" },
|
|
7
|
+
params: { domain: { type: 'string', description: '要查询的域名', descriptionI18n: { en: "Domain to look up" } } },
|
|
8
|
+
async execute({ params, input, use, signal, locale }) {
|
|
9
9
|
const service = use(nettoolsServiceToken);
|
|
10
10
|
const domain = String(params.domain ?? '');
|
|
11
11
|
const callerKey = commandCallerKey(input);
|
|
12
|
-
return service.run(() => service.whois(domain, callerKey, signal));
|
|
12
|
+
return service.run(() => service.whois(domain, callerKey, signal, locale), locale);
|
|
13
13
|
},
|
|
14
14
|
});
|
package/lib/address-policy.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type NettoolsMessage } from './messages.js';
|
|
1
2
|
export type ParsedHost = {
|
|
2
3
|
readonly kind: 'ipv4';
|
|
3
4
|
readonly address: string;
|
|
@@ -15,6 +16,7 @@ export type AddressVerdict = {
|
|
|
15
16
|
} | {
|
|
16
17
|
readonly blocked: true;
|
|
17
18
|
readonly reason: string;
|
|
19
|
+
readonly detail: NettoolsMessage;
|
|
18
20
|
};
|
|
19
21
|
export interface ParseHostOptions {
|
|
20
22
|
/** Allow `_` in labels (needed for DNS names such as `_dmarc.example.com`). */
|
package/lib/address-policy.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { message, formatMessage } from './messages.js';
|
|
1
2
|
/**
|
|
2
3
|
* Target address policy (SSRF guard).
|
|
3
4
|
*
|
|
@@ -26,10 +27,10 @@
|
|
|
26
27
|
import { isIPv4, isIPv6 } from 'node:net';
|
|
27
28
|
import { NettoolsError } from './errors.js';
|
|
28
29
|
const ALLOWED = Object.freeze({ blocked: false });
|
|
29
|
-
function blocked(
|
|
30
|
-
return Object.freeze({ blocked: true, reason });
|
|
30
|
+
function blocked(detail) {
|
|
31
|
+
return Object.freeze({ blocked: true, reason: formatMessage(detail), detail });
|
|
31
32
|
}
|
|
32
|
-
const CLOUD_METADATA_REASON = '
|
|
33
|
+
const CLOUD_METADATA_REASON = message('cloudMetadata');
|
|
33
34
|
/** Exact IPv4 metadata endpoints, checked before the range table for a clearer message. */
|
|
34
35
|
const CLOUD_METADATA_IPV4 = new Set([
|
|
35
36
|
'169.254.169.254', // AWS / GCP / Azure / Oracle / OpenStack instance metadata
|
|
@@ -41,29 +42,29 @@ const CLOUD_METADATA_IPV4 = new Set([
|
|
|
41
42
|
/** Exact IPv6 metadata endpoints. */
|
|
42
43
|
const CLOUD_METADATA_IPV6 = ['fd00:ec2::254', 'fd00:ec2::23'];
|
|
43
44
|
const BLOCKED_IPV4_RANGES = [
|
|
44
|
-
ipv4Rule('0.0.0.0', 8, '
|
|
45
|
-
ipv4Rule('10.0.0.0', 8,
|
|
46
|
-
ipv4Rule('100.64.0.0', 10,
|
|
47
|
-
ipv4Rule('127.0.0.0', 8, '
|
|
48
|
-
ipv4Rule('169.254.0.0', 16, '
|
|
49
|
-
ipv4Rule('172.16.0.0', 12,
|
|
50
|
-
ipv4Rule('192.0.0.0', 24, '
|
|
51
|
-
ipv4Rule('192.0.2.0', 24, '
|
|
52
|
-
ipv4Rule('192.88.99.0', 24, '
|
|
53
|
-
ipv4Rule('192.168.0.0', 16,
|
|
54
|
-
ipv4Rule('198.18.0.0', 15, '
|
|
55
|
-
ipv4Rule('198.51.100.0', 24, '
|
|
56
|
-
ipv4Rule('203.0.113.0', 24, '
|
|
57
|
-
ipv4Rule('224.0.0.0', 4, '
|
|
58
|
-
ipv4Rule('240.0.0.0', 4, '
|
|
45
|
+
ipv4Rule('0.0.0.0', 8, message('unspecifiedV4')),
|
|
46
|
+
ipv4Rule('10.0.0.0', 8, message('privateV4')),
|
|
47
|
+
ipv4Rule('100.64.0.0', 10, message('cgnat')),
|
|
48
|
+
ipv4Rule('127.0.0.0', 8, message('loopback')),
|
|
49
|
+
ipv4Rule('169.254.0.0', 16, message('linkLocal')),
|
|
50
|
+
ipv4Rule('172.16.0.0', 12, message('privateV4')),
|
|
51
|
+
ipv4Rule('192.0.0.0', 24, message('ietfReserved')),
|
|
52
|
+
ipv4Rule('192.0.2.0', 24, message('documentation')),
|
|
53
|
+
ipv4Rule('192.88.99.0', 24, message('relay6to4')),
|
|
54
|
+
ipv4Rule('192.168.0.0', 16, message('privateV4')),
|
|
55
|
+
ipv4Rule('198.18.0.0', 15, message('benchmark')),
|
|
56
|
+
ipv4Rule('198.51.100.0', 24, message('documentation')),
|
|
57
|
+
ipv4Rule('203.0.113.0', 24, message('documentation')),
|
|
58
|
+
ipv4Rule('224.0.0.0', 4, message('multicast')),
|
|
59
|
+
ipv4Rule('240.0.0.0', 4, message('reservedBroadcast')),
|
|
59
60
|
];
|
|
60
61
|
/** Special IPv6 ranges inside 2000::/3 that are still refused. */
|
|
61
62
|
const BLOCKED_GLOBAL_IPV6_RANGES = [
|
|
62
|
-
ipv6Rule('2001::', 32, '
|
|
63
|
-
ipv6Rule('2001:2::', 48, '
|
|
64
|
-
ipv6Rule('2001:10::', 28, '
|
|
65
|
-
ipv6Rule('2001:db8::', 32, '
|
|
66
|
-
ipv6Rule('3fff::', 20, '
|
|
63
|
+
ipv6Rule('2001::', 32, message('teredo')),
|
|
64
|
+
ipv6Rule('2001:2::', 48, message('benchmark')),
|
|
65
|
+
ipv6Rule('2001:10::', 28, message('orchid')),
|
|
66
|
+
ipv6Rule('2001:db8::', 32, message('documentation')),
|
|
67
|
+
ipv6Rule('3fff::', 20, message('documentation')),
|
|
67
68
|
];
|
|
68
69
|
const BLOCKED_HOSTNAMES = new Set([
|
|
69
70
|
'localhost',
|
|
@@ -105,19 +106,19 @@ const MAX_LABEL_LENGTH = 63;
|
|
|
105
106
|
export function parseHostInput(input, options = {}) {
|
|
106
107
|
const trimmed = input.trim();
|
|
107
108
|
if (trimmed.length === 0) {
|
|
108
|
-
throw invalid('
|
|
109
|
+
throw invalid(message('hostRequired'));
|
|
109
110
|
}
|
|
110
111
|
if (trimmed.length > MAX_HOSTNAME_LENGTH + 2) {
|
|
111
|
-
throw invalid('
|
|
112
|
+
throw invalid(message('hostTooLong'));
|
|
112
113
|
}
|
|
113
114
|
if (/[\s/\\@?#%]/u.test(trimmed)) {
|
|
114
|
-
throw invalid('
|
|
115
|
+
throw invalid(message('bareHost'));
|
|
115
116
|
}
|
|
116
117
|
const bracketed = trimmed.startsWith('[') && trimmed.endsWith(']');
|
|
117
118
|
const ipv6Candidate = bracketed ? trimmed.slice(1, -1) : trimmed;
|
|
118
119
|
if (bracketed || trimmed.includes(':')) {
|
|
119
120
|
if (!isIPv6(ipv6Candidate)) {
|
|
120
|
-
throw invalid('
|
|
121
|
+
throw invalid(message('ipv6NoPort'));
|
|
121
122
|
}
|
|
122
123
|
const canonical = canonicalizeWithUrlParser(`[${ipv6Candidate}]`);
|
|
123
124
|
const address = canonical.slice(1, -1);
|
|
@@ -142,33 +143,33 @@ function canonicalizeWithUrlParser(host) {
|
|
|
142
143
|
const result = probe.hostname;
|
|
143
144
|
const unchanged = result === placeholder && host.toLowerCase() !== placeholder;
|
|
144
145
|
if (unchanged || result.length === 0) {
|
|
145
|
-
throw invalid('
|
|
146
|
+
throw invalid(message('invalidHost'));
|
|
146
147
|
}
|
|
147
148
|
return result;
|
|
148
149
|
}
|
|
149
150
|
function validateDomainSyntax(name, options) {
|
|
150
151
|
if (name.length === 0 || name.length > MAX_HOSTNAME_LENGTH) {
|
|
151
|
-
throw invalid('
|
|
152
|
+
throw invalid(message('hostLength'));
|
|
152
153
|
}
|
|
153
154
|
const labels = name.split('.');
|
|
154
155
|
if (labels.length < (options.minLabels ?? 2)) {
|
|
155
|
-
throw invalid('
|
|
156
|
+
throw invalid(message('fullDomain'));
|
|
156
157
|
}
|
|
157
158
|
const labelPattern = options.allowUnderscore
|
|
158
159
|
? /^[a-z0-9_](?:[a-z0-9_-]*[a-z0-9_])?$/u
|
|
159
160
|
: /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
160
161
|
for (const label of labels) {
|
|
161
162
|
if (label.length === 0 || label.length > MAX_LABEL_LENGTH || !labelPattern.test(label)) {
|
|
162
|
-
throw invalid(
|
|
163
|
+
throw invalid(message('invalidLabel', { label: label || message('empty') }));
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
const topLevel = labels[labels.length - 1] ?? '';
|
|
166
167
|
if (/^\d+$/u.test(topLevel)) {
|
|
167
|
-
throw invalid('
|
|
168
|
+
throw invalid(message('numericTld'));
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
|
-
function invalid(
|
|
171
|
-
return new NettoolsError('invalid_input',
|
|
171
|
+
function invalid(detail) {
|
|
172
|
+
return new NettoolsError('invalid_input', detail);
|
|
172
173
|
}
|
|
173
174
|
/* ------------------------------------------------------------------ */
|
|
174
175
|
/* Classification */
|
|
@@ -187,7 +188,7 @@ export function classifyParsedHost(host) {
|
|
|
187
188
|
export function classifyAddress(address) {
|
|
188
189
|
const parsed = parseHostInput(address);
|
|
189
190
|
if (parsed.kind === 'domain') {
|
|
190
|
-
throw invalid(
|
|
191
|
+
throw invalid(message('notIp', { address }));
|
|
191
192
|
}
|
|
192
193
|
return classifyParsedHost(parsed);
|
|
193
194
|
}
|
|
@@ -195,12 +196,12 @@ export function classifyHostname(name) {
|
|
|
195
196
|
const normalized = name.toLowerCase().replace(/\.$/u, '');
|
|
196
197
|
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
|
197
198
|
return normalized.startsWith('localhost')
|
|
198
|
-
? blocked('
|
|
199
|
+
? blocked(message('localhost'))
|
|
199
200
|
: blocked(CLOUD_METADATA_REASON);
|
|
200
201
|
}
|
|
201
202
|
for (const suffix of BLOCKED_SUFFIXES) {
|
|
202
203
|
if (normalized === suffix || normalized.endsWith(`.${suffix}`)) {
|
|
203
|
-
return blocked(
|
|
204
|
+
return blocked(message('reservedSuffix', { suffix }));
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
return ALLOWED;
|
|
@@ -229,11 +230,11 @@ const MULTICAST_PREFIX = ipv6ToBytes('ff00::');
|
|
|
229
230
|
const CLOUD_METADATA_IPV6_BYTES = CLOUD_METADATA_IPV6.map((address) => ipv6ToBytes(address));
|
|
230
231
|
function classifyIpv6Bytes(bytes) {
|
|
231
232
|
if (bytes.every((byte) => byte === 0)) {
|
|
232
|
-
return blocked(
|
|
233
|
+
return blocked(message('unspecifiedV6'));
|
|
233
234
|
}
|
|
234
235
|
const isLoopback = bytes.slice(0, 15).every((byte) => byte === 0) && bytes[15] === 1;
|
|
235
236
|
if (isLoopback) {
|
|
236
|
-
return blocked(
|
|
237
|
+
return blocked(message('loopbackV6'));
|
|
237
238
|
}
|
|
238
239
|
for (const metadata of CLOUD_METADATA_IPV6_BYTES) {
|
|
239
240
|
if (matchesPrefix(bytes, metadata, 128)) {
|
|
@@ -241,31 +242,31 @@ function classifyIpv6Bytes(bytes) {
|
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
if (matchesPrefix(bytes, IPV4_MAPPED_PREFIX, 96)) {
|
|
244
|
-
return classifyEmbeddedIpv4(bytes.slice(12, 16), '
|
|
245
|
+
return classifyEmbeddedIpv4(bytes.slice(12, 16), message('mappedV4'));
|
|
245
246
|
}
|
|
246
247
|
if (matchesPrefix(bytes, NAT64_WELL_KNOWN_PREFIX, 96)) {
|
|
247
|
-
return classifyEmbeddedIpv4(bytes.slice(12, 16), '
|
|
248
|
+
return classifyEmbeddedIpv4(bytes.slice(12, 16), message('nat64'));
|
|
248
249
|
}
|
|
249
250
|
if (matchesPrefix(bytes, NAT64_LOCAL_PREFIX, 48)) {
|
|
250
|
-
return blocked('
|
|
251
|
+
return blocked(message('localNat64'));
|
|
251
252
|
}
|
|
252
253
|
if (matchesPrefix(bytes, SIX_TO_FOUR_PREFIX, 16)) {
|
|
253
|
-
return classifyEmbeddedIpv4(bytes.slice(2, 6), '
|
|
254
|
+
return classifyEmbeddedIpv4(bytes.slice(2, 6), message('sixToFour'));
|
|
254
255
|
}
|
|
255
256
|
if (matchesPrefix(bytes, UNIQUE_LOCAL_PREFIX, 7)) {
|
|
256
|
-
return blocked(
|
|
257
|
+
return blocked(message('ula'));
|
|
257
258
|
}
|
|
258
259
|
if (matchesPrefix(bytes, LINK_LOCAL_PREFIX, 10)) {
|
|
259
|
-
return blocked(
|
|
260
|
+
return blocked(message('linkLocalV6'));
|
|
260
261
|
}
|
|
261
262
|
if (matchesPrefix(bytes, SITE_LOCAL_PREFIX, 10)) {
|
|
262
|
-
return blocked(
|
|
263
|
+
return blocked(message('siteLocalV6'));
|
|
263
264
|
}
|
|
264
265
|
if (matchesPrefix(bytes, MULTICAST_PREFIX, 8)) {
|
|
265
|
-
return blocked(
|
|
266
|
+
return blocked(message('multicastV6'));
|
|
266
267
|
}
|
|
267
268
|
if (!matchesPrefix(bytes, GLOBAL_UNICAST_PREFIX, 3)) {
|
|
268
|
-
return blocked('
|
|
269
|
+
return blocked(message('notGlobalV6'));
|
|
269
270
|
}
|
|
270
271
|
for (const rule of BLOCKED_GLOBAL_IPV6_RANGES) {
|
|
271
272
|
if (matchesPrefix(bytes, rule.prefix, rule.bits)) {
|
|
@@ -280,7 +281,7 @@ function classifyEmbeddedIpv4(embedded, wrapper) {
|
|
|
280
281
|
return verdict;
|
|
281
282
|
}
|
|
282
283
|
const dotted = Array.from(embedded).join('.');
|
|
283
|
-
return blocked(
|
|
284
|
+
return blocked(message('embeddedV4', { wrapper, address: dotted, reason: verdict.detail }));
|
|
284
285
|
}
|
|
285
286
|
/* ------------------------------------------------------------------ */
|
|
286
287
|
/* Byte helpers */
|
|
@@ -307,7 +308,7 @@ export function ipv4ToBytes(address) {
|
|
|
307
308
|
const parts = address.split('.').map((part) => Number(part));
|
|
308
309
|
const valid = parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255);
|
|
309
310
|
if (!valid) {
|
|
310
|
-
throw invalid(
|
|
311
|
+
throw invalid(message('invalidV4', { address }));
|
|
311
312
|
}
|
|
312
313
|
return Uint8Array.from(parts);
|
|
313
314
|
}
|
|
@@ -326,13 +327,13 @@ export function ipv6ToBytes(address) {
|
|
|
326
327
|
}
|
|
327
328
|
const halves = text.split('::');
|
|
328
329
|
if (halves.length > 2) {
|
|
329
|
-
throw invalid(
|
|
330
|
+
throw invalid(message('invalidV6', { address }));
|
|
330
331
|
}
|
|
331
332
|
const head = parseGroups(halves[0] ?? '');
|
|
332
333
|
const rest = halves.length === 2 ? parseGroups(halves[1] ?? '') : [];
|
|
333
334
|
const missing = 8 - head.length - rest.length;
|
|
334
335
|
if (halves.length === 2 ? missing < 1 : missing !== 0) {
|
|
335
|
-
throw invalid(
|
|
336
|
+
throw invalid(message('invalidV6', { address }));
|
|
336
337
|
}
|
|
337
338
|
groups.push(...head);
|
|
338
339
|
for (let index = 0; index < missing && halves.length === 2; index += 1) {
|
|
@@ -355,7 +356,7 @@ function parseGroups(text) {
|
|
|
355
356
|
}
|
|
356
357
|
return text.split(':').map((group) => {
|
|
357
358
|
if (!/^[0-9a-f]{1,4}$/u.test(group)) {
|
|
358
|
-
throw invalid(
|
|
359
|
+
throw invalid(message('invalidV6Group', { group }));
|
|
359
360
|
}
|
|
360
361
|
return Number.parseInt(group, 16);
|
|
361
362
|
});
|
|
@@ -379,7 +380,7 @@ export function reverseLookupName(host) {
|
|
|
379
380
|
}
|
|
380
381
|
return `${nibbles.reverse().join('.')}.ip6.arpa`;
|
|
381
382
|
}
|
|
382
|
-
throw invalid('
|
|
383
|
+
throw invalid(message('reverseNeedsIp'));
|
|
383
384
|
}
|
|
384
385
|
/** Human label for a parsed host. */
|
|
385
386
|
export function displayHost(host) {
|