@zhin.js/plugin-nettools 0.1.2 → 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 +11 -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,16 @@
|
|
|
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
|
+
|
|
3
14
|
## 0.1.2
|
|
4
15
|
|
|
5
16
|
### 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) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"address-policy.js","sourceRoot":"","sources":["../src/address-policy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Target address policy (SSRF guard).\n *\n * Network tools probe hosts that users type into chat. Even though the probes\n * themselves run on third-party services (check-host.net, public DoH, RDAP),\n * we refuse anything that points at private, loopback, link-local, reserved or\n * cloud-metadata space so the bot can never be used to map internal networks\n * or poke metadata endpoints through a relay.\n *\n * Design:\n * 1. Canonicalise first. The input is fed through the WHATWG URL host parser,\n * the same parser browsers and Node's fetch use. It turns odd IPv4 spellings\n * (`2130706433`, `0x7f.1`, `017700000001`, `127.1`) into dotted quads,\n * normalises IPv6 and converts IDN to punycode. We then classify the\n * canonical form, so \"what we checked\" is exactly \"what we send\".\n * 2. Classify by bytes. IPv4 and IPv6 are compared as raw bytes against\n * prefix tables. Embedded IPv4 (IPv4-mapped, NAT64, 6to4) is unwrapped and\n * classified as IPv4.\n * 3. Allow-list IPv6. Only global unicast (2000::/3) is accepted, minus the\n * documentation / Teredo / special ranges listed below.\n * 4. Names. Single-label names, internal suffixes (`.local`, `.internal`, ...)\n * and well-known metadata hostnames are refused before any lookup.\n * Hostnames are additionally resolved (see target-guard.ts) and refused\n * when any A/AAAA record is blocked.\n */\nimport { isIPv4, isIPv6 } from 'node:net';\nimport { NettoolsError } from './errors.js';\n\nexport type ParsedHost =\n | { readonly kind: 'ipv4'; readonly address: string; readonly bytes: Uint8Array }\n | { readonly kind: 'ipv6'; readonly address: string; readonly bytes: Uint8Array }\n | { readonly kind: 'domain'; readonly name: string };\n\nexport type AddressVerdict =\n | { readonly blocked: false }\n | { readonly blocked: true; readonly reason: string };\n\nexport interface ParseHostOptions {\n /** Allow `_` in labels (needed for DNS names such as `_dmarc.example.com`). */\n readonly allowUnderscore?: boolean;\n /** Minimum number of labels for a domain (default 2, i.e. a full domain). */\n readonly minLabels?: number;\n}\n\nconst ALLOWED: AddressVerdict = Object.freeze({ blocked: false });\n\nfunction blocked(reason: string): AddressVerdict {\n return Object.freeze({ blocked: true, reason });\n}\n\n/* ------------------------------------------------------------------ */\n/* Tables */\n/* ------------------------------------------------------------------ */\n\ninterface PrefixRule {\n readonly prefix: Uint8Array;\n readonly bits: number;\n readonly reason: string;\n}\n\nconst CLOUD_METADATA_REASON = '云服务元数据地址';\n\n/** Exact IPv4 metadata endpoints, checked before the range table for a clearer message. */\nconst CLOUD_METADATA_IPV4 = new Set([\n '169.254.169.254', // AWS / GCP / Azure / Oracle / OpenStack instance metadata\n '169.254.170.2', // AWS ECS task metadata\n '169.254.169.123', // AWS Time Sync\n '100.100.100.200', // Alibaba Cloud metadata\n '168.63.129.16', // Azure WireServer (public range, still host-internal)\n]);\n\n/** Exact IPv6 metadata endpoints. */\nconst CLOUD_METADATA_IPV6 = ['fd00:ec2::254', 'fd00:ec2::23'];\n\nconst BLOCKED_IPV4_RANGES: readonly PrefixRule[] = [\n ipv4Rule('0.0.0.0', 8, '未指定 / “本网络”地址'),\n ipv4Rule('10.0.0.0', 8, '私有地址 (RFC 1918)'),\n ipv4Rule('100.64.0.0', 10, '运营商级 NAT 地址 (CGNAT, RFC 6598)'),\n ipv4Rule('127.0.0.0', 8, '回环地址'),\n ipv4Rule('169.254.0.0', 16, '链路本地地址'),\n ipv4Rule('172.16.0.0', 12, '私有地址 (RFC 1918)'),\n ipv4Rule('192.0.0.0', 24, 'IETF 协议保留地址'),\n ipv4Rule('192.0.2.0', 24, '文档示例地址'),\n ipv4Rule('192.88.99.0', 24, '已废弃的 6to4 中继地址'),\n ipv4Rule('192.168.0.0', 16, '私有地址 (RFC 1918)'),\n ipv4Rule('198.18.0.0', 15, '基准测试保留地址'),\n ipv4Rule('198.51.100.0', 24, '文档示例地址'),\n ipv4Rule('203.0.113.0', 24, '文档示例地址'),\n ipv4Rule('224.0.0.0', 4, '组播地址'),\n ipv4Rule('240.0.0.0', 4, '保留地址(含广播地址)'),\n];\n\n/** Special IPv6 ranges inside 2000::/3 that are still refused. */\nconst BLOCKED_GLOBAL_IPV6_RANGES: readonly PrefixRule[] = [\n ipv6Rule('2001::', 32, 'Teredo 隧道地址'),\n ipv6Rule('2001:2::', 48, '基准测试保留地址'),\n ipv6Rule('2001:10::', 28, 'ORCHID 保留地址'),\n ipv6Rule('2001:db8::', 32, '文档示例地址'),\n ipv6Rule('3fff::', 20, '文档示例地址'),\n];\n\nconst BLOCKED_HOSTNAMES = new Set([\n 'localhost',\n 'metadata',\n 'metadata.google.internal',\n 'metadata.goog',\n 'instance-data',\n 'instance-data.ec2.internal',\n]);\n\n/** Suffixes that only make sense inside a private network or are reserved (RFC 2606 / 6761 / 6762). */\nconst BLOCKED_SUFFIXES = [\n 'localhost',\n 'local',\n 'localdomain',\n 'internal',\n 'intranet',\n 'private',\n 'corp',\n 'home',\n 'lan',\n 'home.arpa',\n 'arpa',\n 'test',\n 'invalid',\n 'example',\n 'onion',\n];\n\nconst MAX_HOSTNAME_LENGTH = 253;\nconst MAX_LABEL_LENGTH = 63;\n\n/* ------------------------------------------------------------------ */\n/* Parsing */\n/* ------------------------------------------------------------------ */\n\n/**\n * Parse and canonicalise one user-supplied host (domain, IPv4 or IPv6).\n * Throws {@link NettoolsError} (`invalid_input`) for anything that is not a\n * bare host: URLs, ports, paths, user-info, zone ids and percent-encoding are\n * all refused rather than \"helpfully\" stripped.\n */\nexport function parseHostInput(input: string, options: ParseHostOptions = {}): ParsedHost {\n const trimmed = input.trim();\n if (trimmed.length === 0) {\n throw invalid('请提供主机名或 IP 地址');\n }\n if (trimmed.length > MAX_HOSTNAME_LENGTH + 2) {\n throw invalid('主机名过长');\n }\n if (/[\\s/\\\\@?#%]/u.test(trimmed)) {\n throw invalid('只接受裸主机名或 IP,不要带协议、端口、路径或特殊字符');\n }\n\n const bracketed = trimmed.startsWith('[') && trimmed.endsWith(']');\n const ipv6Candidate = bracketed ? trimmed.slice(1, -1) : trimmed;\n if (bracketed || trimmed.includes(':')) {\n if (!isIPv6(ipv6Candidate)) {\n throw invalid('不是有效的 IPv6 地址(如需端口请去掉端口)');\n }\n const canonical = canonicalizeWithUrlParser(`[${ipv6Candidate}]`);\n const address = canonical.slice(1, -1);\n return Object.freeze({ kind: 'ipv6', address, bytes: ipv6ToBytes(address) });\n }\n\n const canonical = canonicalizeWithUrlParser(trimmed);\n if (isIPv4(canonical)) {\n return Object.freeze({ kind: 'ipv4', address: canonical, bytes: ipv4ToBytes(canonical) });\n }\n const name = canonical.endsWith('.') ? canonical.slice(0, -1) : canonical;\n validateDomainSyntax(name, options);\n return Object.freeze({ kind: 'domain', name });\n}\n\n/**\n * Run the WHATWG host parser by assigning to `URL#hostname` on a placeholder\n * URL. An invalid host leaves the placeholder untouched, which we detect.\n */\nfunction canonicalizeWithUrlParser(host: string): string {\n const placeholder = 'placeholder.invalid';\n const probe = new URL(`http://${placeholder}/`);\n probe.hostname = host;\n const result = probe.hostname;\n const unchanged = result === placeholder && host.toLowerCase() !== placeholder;\n if (unchanged || result.length === 0) {\n throw invalid('不是有效的主机名或 IP 地址');\n }\n return result;\n}\n\nfunction validateDomainSyntax(name: string, options: ParseHostOptions): void {\n if (name.length === 0 || name.length > MAX_HOSTNAME_LENGTH) {\n throw invalid('主机名长度不合法');\n }\n const labels = name.split('.');\n if (labels.length < (options.minLabels ?? 2)) {\n throw invalid('请提供完整域名(例如 example.com)');\n }\n const labelPattern = options.allowUnderscore\n ? /^[a-z0-9_](?:[a-z0-9_-]*[a-z0-9_])?$/u\n : /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;\n for (const label of labels) {\n if (label.length === 0 || label.length > MAX_LABEL_LENGTH || !labelPattern.test(label)) {\n throw invalid(`域名标签不合法:${label || '(空)'}`);\n }\n }\n const topLevel = labels[labels.length - 1] ?? '';\n if (/^\\d+$/u.test(topLevel)) {\n throw invalid('顶级域不能是纯数字');\n }\n}\n\nfunction invalid(message: string): NettoolsError {\n return new NettoolsError('invalid_input', message);\n}\n\n/* ------------------------------------------------------------------ */\n/* Classification */\n/* ------------------------------------------------------------------ */\n\n/** Classify a parsed host without any network access. Domains are checked by name only. */\nexport function classifyParsedHost(host: ParsedHost): AddressVerdict {\n if (host.kind === 'domain') {\n return classifyHostname(host.name);\n }\n if (host.kind === 'ipv4') {\n return classifyIpv4Bytes(host.bytes);\n }\n return classifyIpv6Bytes(host.bytes);\n}\n\n/** Classify a literal address string (any spelling the URL parser understands). */\nexport function classifyAddress(address: string): AddressVerdict {\n const parsed = parseHostInput(address);\n if (parsed.kind === 'domain') {\n throw invalid(`不是 IP 地址:${address}`);\n }\n return classifyParsedHost(parsed);\n}\n\nexport function classifyHostname(name: string): AddressVerdict {\n const normalized = name.toLowerCase().replace(/\\.$/u, '');\n if (BLOCKED_HOSTNAMES.has(normalized)) {\n return normalized.startsWith('localhost')\n ? blocked('本机主机名')\n : blocked(CLOUD_METADATA_REASON);\n }\n for (const suffix of BLOCKED_SUFFIXES) {\n if (normalized === suffix || normalized.endsWith(`.${suffix}`)) {\n return blocked(`内网或保留域名后缀 .${suffix}`);\n }\n }\n return ALLOWED;\n}\n\nfunction classifyIpv4Bytes(bytes: Uint8Array): AddressVerdict {\n const dotted = Array.from(bytes).join('.');\n if (CLOUD_METADATA_IPV4.has(dotted)) {\n return blocked(CLOUD_METADATA_REASON);\n }\n for (const rule of BLOCKED_IPV4_RANGES) {\n if (matchesPrefix(bytes, rule.prefix, rule.bits)) {\n return blocked(rule.reason);\n }\n }\n return ALLOWED;\n}\n\nconst IPV4_MAPPED_PREFIX = ipv6ToBytes('::ffff:0:0');\nconst NAT64_WELL_KNOWN_PREFIX = ipv6ToBytes('64:ff9b::');\nconst NAT64_LOCAL_PREFIX = ipv6ToBytes('64:ff9b:1::');\nconst SIX_TO_FOUR_PREFIX = ipv6ToBytes('2002::');\nconst GLOBAL_UNICAST_PREFIX = ipv6ToBytes('2000::');\nconst UNIQUE_LOCAL_PREFIX = ipv6ToBytes('fc00::');\nconst LINK_LOCAL_PREFIX = ipv6ToBytes('fe80::');\nconst SITE_LOCAL_PREFIX = ipv6ToBytes('fec0::');\nconst MULTICAST_PREFIX = ipv6ToBytes('ff00::');\nconst CLOUD_METADATA_IPV6_BYTES = CLOUD_METADATA_IPV6.map((address) => ipv6ToBytes(address));\n\nfunction classifyIpv6Bytes(bytes: Uint8Array): AddressVerdict {\n if (bytes.every((byte) => byte === 0)) {\n return blocked('未指定地址 (::)');\n }\n const isLoopback = bytes.slice(0, 15).every((byte) => byte === 0) && bytes[15] === 1;\n if (isLoopback) {\n return blocked('回环地址 (::1)');\n }\n for (const metadata of CLOUD_METADATA_IPV6_BYTES) {\n if (matchesPrefix(bytes, metadata, 128)) {\n return blocked(CLOUD_METADATA_REASON);\n }\n }\n if (matchesPrefix(bytes, IPV4_MAPPED_PREFIX, 96)) {\n return classifyEmbeddedIpv4(bytes.slice(12, 16), 'IPv4 映射地址');\n }\n if (matchesPrefix(bytes, NAT64_WELL_KNOWN_PREFIX, 96)) {\n return classifyEmbeddedIpv4(bytes.slice(12, 16), 'NAT64 地址');\n }\n if (matchesPrefix(bytes, NAT64_LOCAL_PREFIX, 48)) {\n return blocked('本地 NAT64 地址');\n }\n if (matchesPrefix(bytes, SIX_TO_FOUR_PREFIX, 16)) {\n return classifyEmbeddedIpv4(bytes.slice(2, 6), '6to4 地址');\n }\n if (matchesPrefix(bytes, UNIQUE_LOCAL_PREFIX, 7)) {\n return blocked('唯一本地地址 (ULA, fc00::/7)');\n }\n if (matchesPrefix(bytes, LINK_LOCAL_PREFIX, 10)) {\n return blocked('链路本地地址 (fe80::/10)');\n }\n if (matchesPrefix(bytes, SITE_LOCAL_PREFIX, 10)) {\n return blocked('已废弃的站点本地地址 (fec0::/10)');\n }\n if (matchesPrefix(bytes, MULTICAST_PREFIX, 8)) {\n return blocked('组播地址 (ff00::/8)');\n }\n if (!matchesPrefix(bytes, GLOBAL_UNICAST_PREFIX, 3)) {\n return blocked('非全球单播 IPv6 地址');\n }\n for (const rule of BLOCKED_GLOBAL_IPV6_RANGES) {\n if (matchesPrefix(bytes, rule.prefix, rule.bits)) {\n return blocked(rule.reason);\n }\n }\n return ALLOWED;\n}\n\nfunction classifyEmbeddedIpv4(embedded: Uint8Array, wrapper: string): AddressVerdict {\n const verdict = classifyIpv4Bytes(embedded);\n if (!verdict.blocked) {\n return verdict;\n }\n const dotted = Array.from(embedded).join('.');\n return blocked(`${wrapper}指向 ${dotted}(${verdict.reason})`);\n}\n\n/* ------------------------------------------------------------------ */\n/* Byte helpers */\n/* ------------------------------------------------------------------ */\n\nfunction matchesPrefix(bytes: Uint8Array, prefix: Uint8Array, bits: number): boolean {\n if (bytes.length !== prefix.length) {\n return false;\n }\n const fullBytes = Math.floor(bits / 8);\n for (let index = 0; index < fullBytes; index += 1) {\n if (bytes[index] !== prefix[index]) {\n return false;\n }\n }\n const remainingBits = bits % 8;\n if (remainingBits === 0) {\n return true;\n }\n const mask = (0xff << (8 - remainingBits)) & 0xff;\n return ((bytes[fullBytes] ?? 0) & mask) === ((prefix[fullBytes] ?? 0) & mask);\n}\n\n/** Dotted-quad IPv4 to 4 bytes. Callers must pass a canonical dotted quad. */\nexport function ipv4ToBytes(address: string): Uint8Array {\n const parts = address.split('.').map((part) => Number(part));\n const valid = parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255);\n if (!valid) {\n throw invalid(`不是有效的 IPv4 地址:${address}`);\n }\n return Uint8Array.from(parts);\n}\n\n/** Textual IPv6 (with optional `::` and dotted IPv4 tail) to 16 bytes. */\nexport function ipv6ToBytes(address: string): Uint8Array {\n let text = address.toLowerCase();\n const groups: number[] = [];\n const lastColon = text.lastIndexOf(':');\n const tail = text.slice(lastColon + 1);\n const tailGroups: number[] = [];\n if (tail.includes('.')) {\n const v4 = ipv4ToBytes(tail);\n tailGroups.push(((v4[0] ?? 0) << 8) | (v4[1] ?? 0));\n tailGroups.push(((v4[2] ?? 0) << 8) | (v4[3] ?? 0));\n text = text.slice(0, lastColon + 1) + '0:0';\n }\n const halves = text.split('::');\n if (halves.length > 2) {\n throw invalid(`不是有效的 IPv6 地址:${address}`);\n }\n const head = parseGroups(halves[0] ?? '');\n const rest = halves.length === 2 ? parseGroups(halves[1] ?? '') : [];\n const missing = 8 - head.length - rest.length;\n if (halves.length === 2 ? missing < 1 : missing !== 0) {\n throw invalid(`不是有效的 IPv6 地址:${address}`);\n }\n groups.push(...head);\n for (let index = 0; index < missing && halves.length === 2; index += 1) {\n groups.push(0);\n }\n groups.push(...rest);\n if (tailGroups.length === 2) {\n groups.splice(6, 2, ...tailGroups);\n }\n const bytes = new Uint8Array(16);\n groups.forEach((group, index) => {\n bytes[index * 2] = (group >> 8) & 0xff;\n bytes[index * 2 + 1] = group & 0xff;\n });\n return bytes;\n}\n\nfunction parseGroups(text: string): number[] {\n if (text.length === 0) {\n return [];\n }\n return text.split(':').map((group) => {\n if (!/^[0-9a-f]{1,4}$/u.test(group)) {\n throw invalid(`IPv6 分组不合法:${group}`);\n }\n return Number.parseInt(group, 16);\n });\n}\n\nfunction ipv4Rule(base: string, bits: number, reason: string): PrefixRule {\n return Object.freeze({ prefix: ipv4ToBytes(base), bits, reason });\n}\n\nfunction ipv6Rule(base: string, bits: number, reason: string): PrefixRule {\n return Object.freeze({ prefix: ipv6ToBytes(base), bits, reason });\n}\n\n/** Build the PTR query name for an IP (`in-addr.arpa` / `ip6.arpa`). */\nexport function reverseLookupName(host: ParsedHost): string {\n if (host.kind === 'ipv4') {\n return `${Array.from(host.bytes).reverse().join('.')}.in-addr.arpa`;\n }\n if (host.kind === 'ipv6') {\n const nibbles: string[] = [];\n for (const byte of host.bytes) {\n nibbles.push((byte >> 4).toString(16));\n nibbles.push((byte & 0x0f).toString(16));\n }\n return `${nibbles.reverse().join('.')}.ip6.arpa`;\n }\n throw invalid('反向解析需要 IP 地址');\n}\n\n/** Human label for a parsed host. */\nexport function displayHost(host: ParsedHost): string {\n return host.kind === 'domain' ? host.name : host.address;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"address-policy.js","sourceRoot":"","sources":["../src/address-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import { message, formatMessage, type NettoolsMessage } from './messages.js';\n/**\n * Target address policy (SSRF guard).\n *\n * Network tools probe hosts that users type into chat. Even though the probes\n * themselves run on third-party services (check-host.net, public DoH, RDAP),\n * we refuse anything that points at private, loopback, link-local, reserved or\n * cloud-metadata space so the bot can never be used to map internal networks\n * or poke metadata endpoints through a relay.\n *\n * Design:\n * 1. Canonicalise first. The input is fed through the WHATWG URL host parser,\n * the same parser browsers and Node's fetch use. It turns odd IPv4 spellings\n * (`2130706433`, `0x7f.1`, `017700000001`, `127.1`) into dotted quads,\n * normalises IPv6 and converts IDN to punycode. We then classify the\n * canonical form, so \"what we checked\" is exactly \"what we send\".\n * 2. Classify by bytes. IPv4 and IPv6 are compared as raw bytes against\n * prefix tables. Embedded IPv4 (IPv4-mapped, NAT64, 6to4) is unwrapped and\n * classified as IPv4.\n * 3. Allow-list IPv6. Only global unicast (2000::/3) is accepted, minus the\n * documentation / Teredo / special ranges listed below.\n * 4. Names. Single-label names, internal suffixes (`.local`, `.internal`, ...)\n * and well-known metadata hostnames are refused before any lookup.\n * Hostnames are additionally resolved (see target-guard.ts) and refused\n * when any A/AAAA record is blocked.\n */\nimport { isIPv4, isIPv6 } from 'node:net';\nimport { NettoolsError } from './errors.js';\n\nexport type ParsedHost =\n | { readonly kind: 'ipv4'; readonly address: string; readonly bytes: Uint8Array }\n | { readonly kind: 'ipv6'; readonly address: string; readonly bytes: Uint8Array }\n | { readonly kind: 'domain'; readonly name: string };\n\nexport type AddressVerdict =\n | { readonly blocked: false }\n | { readonly blocked: true; readonly reason: string; readonly detail: NettoolsMessage };\n\nexport interface ParseHostOptions {\n /** Allow `_` in labels (needed for DNS names such as `_dmarc.example.com`). */\n readonly allowUnderscore?: boolean;\n /** Minimum number of labels for a domain (default 2, i.e. a full domain). */\n readonly minLabels?: number;\n}\n\nconst ALLOWED: AddressVerdict = Object.freeze({ blocked: false });\n\nfunction blocked(detail: NettoolsMessage): AddressVerdict {\n return Object.freeze({ blocked: true, reason: formatMessage(detail), detail });\n}\n\n/* ------------------------------------------------------------------ */\n/* Tables */\n/* ------------------------------------------------------------------ */\n\ninterface PrefixRule {\n readonly prefix: Uint8Array;\n readonly bits: number;\n readonly reason: NettoolsMessage;\n}\n\nconst CLOUD_METADATA_REASON = message('cloudMetadata');\n\n/** Exact IPv4 metadata endpoints, checked before the range table for a clearer message. */\nconst CLOUD_METADATA_IPV4 = new Set([\n '169.254.169.254', // AWS / GCP / Azure / Oracle / OpenStack instance metadata\n '169.254.170.2', // AWS ECS task metadata\n '169.254.169.123', // AWS Time Sync\n '100.100.100.200', // Alibaba Cloud metadata\n '168.63.129.16', // Azure WireServer (public range, still host-internal)\n]);\n\n/** Exact IPv6 metadata endpoints. */\nconst CLOUD_METADATA_IPV6 = ['fd00:ec2::254', 'fd00:ec2::23'];\n\nconst BLOCKED_IPV4_RANGES: readonly PrefixRule[] = [\n ipv4Rule('0.0.0.0', 8, message('unspecifiedV4')),\n ipv4Rule('10.0.0.0', 8, message('privateV4')),\n ipv4Rule('100.64.0.0', 10, message('cgnat')),\n ipv4Rule('127.0.0.0', 8, message('loopback')),\n ipv4Rule('169.254.0.0', 16, message('linkLocal')),\n ipv4Rule('172.16.0.0', 12, message('privateV4')),\n ipv4Rule('192.0.0.0', 24, message('ietfReserved')),\n ipv4Rule('192.0.2.0', 24, message('documentation')),\n ipv4Rule('192.88.99.0', 24, message('relay6to4')),\n ipv4Rule('192.168.0.0', 16, message('privateV4')),\n ipv4Rule('198.18.0.0', 15, message('benchmark')),\n ipv4Rule('198.51.100.0', 24, message('documentation')),\n ipv4Rule('203.0.113.0', 24, message('documentation')),\n ipv4Rule('224.0.0.0', 4, message('multicast')),\n ipv4Rule('240.0.0.0', 4, message('reservedBroadcast')),\n];\n\n/** Special IPv6 ranges inside 2000::/3 that are still refused. */\nconst BLOCKED_GLOBAL_IPV6_RANGES: readonly PrefixRule[] = [\n ipv6Rule('2001::', 32, message('teredo')),\n ipv6Rule('2001:2::', 48, message('benchmark')),\n ipv6Rule('2001:10::', 28, message('orchid')),\n ipv6Rule('2001:db8::', 32, message('documentation')),\n ipv6Rule('3fff::', 20, message('documentation')),\n];\n\nconst BLOCKED_HOSTNAMES = new Set([\n 'localhost',\n 'metadata',\n 'metadata.google.internal',\n 'metadata.goog',\n 'instance-data',\n 'instance-data.ec2.internal',\n]);\n\n/** Suffixes that only make sense inside a private network or are reserved (RFC 2606 / 6761 / 6762). */\nconst BLOCKED_SUFFIXES = [\n 'localhost',\n 'local',\n 'localdomain',\n 'internal',\n 'intranet',\n 'private',\n 'corp',\n 'home',\n 'lan',\n 'home.arpa',\n 'arpa',\n 'test',\n 'invalid',\n 'example',\n 'onion',\n];\n\nconst MAX_HOSTNAME_LENGTH = 253;\nconst MAX_LABEL_LENGTH = 63;\n\n/* ------------------------------------------------------------------ */\n/* Parsing */\n/* ------------------------------------------------------------------ */\n\n/**\n * Parse and canonicalise one user-supplied host (domain, IPv4 or IPv6).\n * Throws {@link NettoolsError} (`invalid_input`) for anything that is not a\n * bare host: URLs, ports, paths, user-info, zone ids and percent-encoding are\n * all refused rather than \"helpfully\" stripped.\n */\nexport function parseHostInput(input: string, options: ParseHostOptions = {}): ParsedHost {\n const trimmed = input.trim();\n if (trimmed.length === 0) {\n throw invalid(message('hostRequired'));\n }\n if (trimmed.length > MAX_HOSTNAME_LENGTH + 2) {\n throw invalid(message('hostTooLong'));\n }\n if (/[\\s/\\\\@?#%]/u.test(trimmed)) {\n throw invalid(message('bareHost'));\n }\n\n const bracketed = trimmed.startsWith('[') && trimmed.endsWith(']');\n const ipv6Candidate = bracketed ? trimmed.slice(1, -1) : trimmed;\n if (bracketed || trimmed.includes(':')) {\n if (!isIPv6(ipv6Candidate)) {\n throw invalid(message('ipv6NoPort'));\n }\n const canonical = canonicalizeWithUrlParser(`[${ipv6Candidate}]`);\n const address = canonical.slice(1, -1);\n return Object.freeze({ kind: 'ipv6', address, bytes: ipv6ToBytes(address) });\n }\n\n const canonical = canonicalizeWithUrlParser(trimmed);\n if (isIPv4(canonical)) {\n return Object.freeze({ kind: 'ipv4', address: canonical, bytes: ipv4ToBytes(canonical) });\n }\n const name = canonical.endsWith('.') ? canonical.slice(0, -1) : canonical;\n validateDomainSyntax(name, options);\n return Object.freeze({ kind: 'domain', name });\n}\n\n/**\n * Run the WHATWG host parser by assigning to `URL#hostname` on a placeholder\n * URL. An invalid host leaves the placeholder untouched, which we detect.\n */\nfunction canonicalizeWithUrlParser(host: string): string {\n const placeholder = 'placeholder.invalid';\n const probe = new URL(`http://${placeholder}/`);\n probe.hostname = host;\n const result = probe.hostname;\n const unchanged = result === placeholder && host.toLowerCase() !== placeholder;\n if (unchanged || result.length === 0) {\n throw invalid(message('invalidHost'));\n }\n return result;\n}\n\nfunction validateDomainSyntax(name: string, options: ParseHostOptions): void {\n if (name.length === 0 || name.length > MAX_HOSTNAME_LENGTH) {\n throw invalid(message('hostLength'));\n }\n const labels = name.split('.');\n if (labels.length < (options.minLabels ?? 2)) {\n throw invalid(message('fullDomain'));\n }\n const labelPattern = options.allowUnderscore\n ? /^[a-z0-9_](?:[a-z0-9_-]*[a-z0-9_])?$/u\n : /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;\n for (const label of labels) {\n if (label.length === 0 || label.length > MAX_LABEL_LENGTH || !labelPattern.test(label)) {\n throw invalid(message('invalidLabel', { label: label || message('empty') }));\n }\n }\n const topLevel = labels[labels.length - 1] ?? '';\n if (/^\\d+$/u.test(topLevel)) {\n throw invalid(message('numericTld'));\n }\n}\n\nfunction invalid(detail: NettoolsMessage): NettoolsError {\n return new NettoolsError('invalid_input', detail);\n}\n\n/* ------------------------------------------------------------------ */\n/* Classification */\n/* ------------------------------------------------------------------ */\n\n/** Classify a parsed host without any network access. Domains are checked by name only. */\nexport function classifyParsedHost(host: ParsedHost): AddressVerdict {\n if (host.kind === 'domain') {\n return classifyHostname(host.name);\n }\n if (host.kind === 'ipv4') {\n return classifyIpv4Bytes(host.bytes);\n }\n return classifyIpv6Bytes(host.bytes);\n}\n\n/** Classify a literal address string (any spelling the URL parser understands). */\nexport function classifyAddress(address: string): AddressVerdict {\n const parsed = parseHostInput(address);\n if (parsed.kind === 'domain') {\n throw invalid(message('notIp', { address }));\n }\n return classifyParsedHost(parsed);\n}\n\nexport function classifyHostname(name: string): AddressVerdict {\n const normalized = name.toLowerCase().replace(/\\.$/u, '');\n if (BLOCKED_HOSTNAMES.has(normalized)) {\n return normalized.startsWith('localhost')\n ? blocked(message('localhost'))\n : blocked(CLOUD_METADATA_REASON);\n }\n for (const suffix of BLOCKED_SUFFIXES) {\n if (normalized === suffix || normalized.endsWith(`.${suffix}`)) {\n return blocked(message('reservedSuffix', { suffix }));\n }\n }\n return ALLOWED;\n}\n\nfunction classifyIpv4Bytes(bytes: Uint8Array): AddressVerdict {\n const dotted = Array.from(bytes).join('.');\n if (CLOUD_METADATA_IPV4.has(dotted)) {\n return blocked(CLOUD_METADATA_REASON);\n }\n for (const rule of BLOCKED_IPV4_RANGES) {\n if (matchesPrefix(bytes, rule.prefix, rule.bits)) {\n return blocked(rule.reason);\n }\n }\n return ALLOWED;\n}\n\nconst IPV4_MAPPED_PREFIX = ipv6ToBytes('::ffff:0:0');\nconst NAT64_WELL_KNOWN_PREFIX = ipv6ToBytes('64:ff9b::');\nconst NAT64_LOCAL_PREFIX = ipv6ToBytes('64:ff9b:1::');\nconst SIX_TO_FOUR_PREFIX = ipv6ToBytes('2002::');\nconst GLOBAL_UNICAST_PREFIX = ipv6ToBytes('2000::');\nconst UNIQUE_LOCAL_PREFIX = ipv6ToBytes('fc00::');\nconst LINK_LOCAL_PREFIX = ipv6ToBytes('fe80::');\nconst SITE_LOCAL_PREFIX = ipv6ToBytes('fec0::');\nconst MULTICAST_PREFIX = ipv6ToBytes('ff00::');\nconst CLOUD_METADATA_IPV6_BYTES = CLOUD_METADATA_IPV6.map((address) => ipv6ToBytes(address));\n\nfunction classifyIpv6Bytes(bytes: Uint8Array): AddressVerdict {\n if (bytes.every((byte) => byte === 0)) {\n return blocked(message('unspecifiedV6'));\n }\n const isLoopback = bytes.slice(0, 15).every((byte) => byte === 0) && bytes[15] === 1;\n if (isLoopback) {\n return blocked(message('loopbackV6'));\n }\n for (const metadata of CLOUD_METADATA_IPV6_BYTES) {\n if (matchesPrefix(bytes, metadata, 128)) {\n return blocked(CLOUD_METADATA_REASON);\n }\n }\n if (matchesPrefix(bytes, IPV4_MAPPED_PREFIX, 96)) {\n return classifyEmbeddedIpv4(bytes.slice(12, 16), message('mappedV4'));\n }\n if (matchesPrefix(bytes, NAT64_WELL_KNOWN_PREFIX, 96)) {\n return classifyEmbeddedIpv4(bytes.slice(12, 16), message('nat64'));\n }\n if (matchesPrefix(bytes, NAT64_LOCAL_PREFIX, 48)) {\n return blocked(message('localNat64'));\n }\n if (matchesPrefix(bytes, SIX_TO_FOUR_PREFIX, 16)) {\n return classifyEmbeddedIpv4(bytes.slice(2, 6), message('sixToFour'));\n }\n if (matchesPrefix(bytes, UNIQUE_LOCAL_PREFIX, 7)) {\n return blocked(message('ula'));\n }\n if (matchesPrefix(bytes, LINK_LOCAL_PREFIX, 10)) {\n return blocked(message('linkLocalV6'));\n }\n if (matchesPrefix(bytes, SITE_LOCAL_PREFIX, 10)) {\n return blocked(message('siteLocalV6'));\n }\n if (matchesPrefix(bytes, MULTICAST_PREFIX, 8)) {\n return blocked(message('multicastV6'));\n }\n if (!matchesPrefix(bytes, GLOBAL_UNICAST_PREFIX, 3)) {\n return blocked(message('notGlobalV6'));\n }\n for (const rule of BLOCKED_GLOBAL_IPV6_RANGES) {\n if (matchesPrefix(bytes, rule.prefix, rule.bits)) {\n return blocked(rule.reason);\n }\n }\n return ALLOWED;\n}\n\nfunction classifyEmbeddedIpv4(embedded: Uint8Array, wrapper: NettoolsMessage): AddressVerdict {\n const verdict = classifyIpv4Bytes(embedded);\n if (!verdict.blocked) {\n return verdict;\n }\n const dotted = Array.from(embedded).join('.');\n return blocked(message('embeddedV4', { wrapper, address: dotted, reason: verdict.detail }));\n}\n\n/* ------------------------------------------------------------------ */\n/* Byte helpers */\n/* ------------------------------------------------------------------ */\n\nfunction matchesPrefix(bytes: Uint8Array, prefix: Uint8Array, bits: number): boolean {\n if (bytes.length !== prefix.length) {\n return false;\n }\n const fullBytes = Math.floor(bits / 8);\n for (let index = 0; index < fullBytes; index += 1) {\n if (bytes[index] !== prefix[index]) {\n return false;\n }\n }\n const remainingBits = bits % 8;\n if (remainingBits === 0) {\n return true;\n }\n const mask = (0xff << (8 - remainingBits)) & 0xff;\n return ((bytes[fullBytes] ?? 0) & mask) === ((prefix[fullBytes] ?? 0) & mask);\n}\n\n/** Dotted-quad IPv4 to 4 bytes. Callers must pass a canonical dotted quad. */\nexport function ipv4ToBytes(address: string): Uint8Array {\n const parts = address.split('.').map((part) => Number(part));\n const valid = parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255);\n if (!valid) {\n throw invalid(message('invalidV4', { address }));\n }\n return Uint8Array.from(parts);\n}\n\n/** Textual IPv6 (with optional `::` and dotted IPv4 tail) to 16 bytes. */\nexport function ipv6ToBytes(address: string): Uint8Array {\n let text = address.toLowerCase();\n const groups: number[] = [];\n const lastColon = text.lastIndexOf(':');\n const tail = text.slice(lastColon + 1);\n const tailGroups: number[] = [];\n if (tail.includes('.')) {\n const v4 = ipv4ToBytes(tail);\n tailGroups.push(((v4[0] ?? 0) << 8) | (v4[1] ?? 0));\n tailGroups.push(((v4[2] ?? 0) << 8) | (v4[3] ?? 0));\n text = text.slice(0, lastColon + 1) + '0:0';\n }\n const halves = text.split('::');\n if (halves.length > 2) {\n throw invalid(message('invalidV6', { address }));\n }\n const head = parseGroups(halves[0] ?? '');\n const rest = halves.length === 2 ? parseGroups(halves[1] ?? '') : [];\n const missing = 8 - head.length - rest.length;\n if (halves.length === 2 ? missing < 1 : missing !== 0) {\n throw invalid(message('invalidV6', { address }));\n }\n groups.push(...head);\n for (let index = 0; index < missing && halves.length === 2; index += 1) {\n groups.push(0);\n }\n groups.push(...rest);\n if (tailGroups.length === 2) {\n groups.splice(6, 2, ...tailGroups);\n }\n const bytes = new Uint8Array(16);\n groups.forEach((group, index) => {\n bytes[index * 2] = (group >> 8) & 0xff;\n bytes[index * 2 + 1] = group & 0xff;\n });\n return bytes;\n}\n\nfunction parseGroups(text: string): number[] {\n if (text.length === 0) {\n return [];\n }\n return text.split(':').map((group) => {\n if (!/^[0-9a-f]{1,4}$/u.test(group)) {\n throw invalid(message('invalidV6Group', { group }));\n }\n return Number.parseInt(group, 16);\n });\n}\n\nfunction ipv4Rule(base: string, bits: number, reason: NettoolsMessage): PrefixRule {\n return Object.freeze({ prefix: ipv4ToBytes(base), bits, reason });\n}\n\nfunction ipv6Rule(base: string, bits: number, reason: NettoolsMessage): PrefixRule {\n return Object.freeze({ prefix: ipv6ToBytes(base), bits, reason });\n}\n\n/** Build the PTR query name for an IP (`in-addr.arpa` / `ip6.arpa`). */\nexport function reverseLookupName(host: ParsedHost): string {\n if (host.kind === 'ipv4') {\n return `${Array.from(host.bytes).reverse().join('.')}.in-addr.arpa`;\n }\n if (host.kind === 'ipv6') {\n const nibbles: string[] = [];\n for (const byte of host.bytes) {\n nibbles.push((byte >> 4).toString(16));\n nibbles.push((byte & 0x0f).toString(16));\n }\n return `${nibbles.reverse().join('.')}.ip6.arpa`;\n }\n throw invalid(message('reverseNeedsIp'));\n}\n\n/** Human label for a parsed host. */\nexport function displayHost(host: ParsedHost): string {\n return host.kind === 'domain' ? host.name : host.address;\n}\n"]}
|