nstarter-http-request 0.1.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/CHANGELOG.md +5 -0
- package/README.md +160 -0
- package/dist/cjs/adapter/axios.adapter.js +224 -0
- package/dist/cjs/adapter/axios.adapter.js.map +1 -0
- package/dist/cjs/adapter/base.js +134 -0
- package/dist/cjs/adapter/base.js.map +1 -0
- package/dist/cjs/adapter/index.js +20 -0
- package/dist/cjs/adapter/index.js.map +1 -0
- package/dist/cjs/adapter/undici.adapter.js +272 -0
- package/dist/cjs/adapter/undici.adapter.js.map +1 -0
- package/dist/cjs/client.js +409 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/config.js +69 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/const/config.const.js +53 -0
- package/dist/cjs/const/config.const.js.map +1 -0
- package/dist/cjs/const/dns.const.js +16 -0
- package/dist/cjs/const/dns.const.js.map +1 -0
- package/dist/cjs/const/enum.const.js +22 -0
- package/dist/cjs/const/enum.const.js.map +1 -0
- package/dist/cjs/const/index.js +21 -0
- package/dist/cjs/const/index.js.map +1 -0
- package/dist/cjs/const/ip.const.js +110 -0
- package/dist/cjs/const/ip.const.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/logger/base.js +60 -0
- package/dist/cjs/logger/base.js.map +1 -0
- package/dist/cjs/logger/http_client.logger.js +209 -0
- package/dist/cjs/logger/http_client.logger.js.map +1 -0
- package/dist/cjs/logger/index.js +20 -0
- package/dist/cjs/logger/index.js.map +1 -0
- package/dist/cjs/logger/log.filter.js +126 -0
- package/dist/cjs/logger/log.filter.js.map +1 -0
- package/dist/cjs/security/dns.validator.js +137 -0
- package/dist/cjs/security/dns.validator.js.map +1 -0
- package/dist/cjs/security/index.js +21 -0
- package/dist/cjs/security/index.js.map +1 -0
- package/dist/cjs/security/ip.validator.js +107 -0
- package/dist/cjs/security/ip.validator.js.map +1 -0
- package/dist/cjs/security/ssrf.guard.js +180 -0
- package/dist/cjs/security/ssrf.guard.js.map +1 -0
- package/dist/cjs/security/url.validator.js +170 -0
- package/dist/cjs/security/url.validator.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/cjs/types/adapter.js +6 -0
- package/dist/cjs/types/adapter.js.map +1 -0
- package/dist/cjs/types/client.js +6 -0
- package/dist/cjs/types/client.js.map +1 -0
- package/dist/cjs/types/config.js +6 -0
- package/dist/cjs/types/config.js.map +1 -0
- package/dist/cjs/types/errors.js +35 -0
- package/dist/cjs/types/errors.js.map +1 -0
- package/dist/cjs/types/index.js +35 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/types/ip.js +6 -0
- package/dist/cjs/types/ip.js.map +1 -0
- package/dist/cjs/types/logger.js +6 -0
- package/dist/cjs/types/logger.js.map +1 -0
- package/dist/cjs/types/request_response.js +6 -0
- package/dist/cjs/types/request_response.js.map +1 -0
- package/dist/cjs/types/security.js +6 -0
- package/dist/cjs/types/security.js.map +1 -0
- package/dist/cjs/types/trace.js +14 -0
- package/dist/cjs/types/trace.js.map +1 -0
- package/dist/cjs/utils/common.js +31 -0
- package/dist/cjs/utils/common.js.map +1 -0
- package/dist/cjs/utils/domain.js +79 -0
- package/dist/cjs/utils/domain.js.map +1 -0
- package/dist/cjs/utils/index.js +44 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/ip.range.js +200 -0
- package/dist/cjs/utils/ip.range.js.map +1 -0
- package/dist/cjs/utils/trace.context.js +213 -0
- package/dist/cjs/utils/trace.context.js.map +1 -0
- package/dist/esm/adapter/axios.adapter.js +184 -0
- package/dist/esm/adapter/axios.adapter.js.map +1 -0
- package/dist/esm/adapter/base.js +130 -0
- package/dist/esm/adapter/base.js.map +1 -0
- package/dist/esm/adapter/index.js +4 -0
- package/dist/esm/adapter/index.js.map +1 -0
- package/dist/esm/adapter/undici.adapter.js +235 -0
- package/dist/esm/adapter/undici.adapter.js.map +1 -0
- package/dist/esm/client.js +405 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/config.js +65 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/const/config.const.js +50 -0
- package/dist/esm/const/config.const.js.map +1 -0
- package/dist/esm/const/dns.const.js +13 -0
- package/dist/esm/const/dns.const.js.map +1 -0
- package/dist/esm/const/enum.const.js +19 -0
- package/dist/esm/const/enum.const.js.map +1 -0
- package/dist/esm/const/index.js +5 -0
- package/dist/esm/const/index.js.map +1 -0
- package/dist/esm/const/ip.const.js +107 -0
- package/dist/esm/const/ip.const.js.map +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger/base.js +55 -0
- package/dist/esm/logger/base.js.map +1 -0
- package/dist/esm/logger/http_client.logger.js +205 -0
- package/dist/esm/logger/http_client.logger.js.map +1 -0
- package/dist/esm/logger/index.js +4 -0
- package/dist/esm/logger/index.js.map +1 -0
- package/dist/esm/logger/log.filter.js +122 -0
- package/dist/esm/logger/log.filter.js.map +1 -0
- package/dist/esm/security/dns.validator.js +133 -0
- package/dist/esm/security/dns.validator.js.map +1 -0
- package/dist/esm/security/index.js +5 -0
- package/dist/esm/security/index.js.map +1 -0
- package/dist/esm/security/ip.validator.js +103 -0
- package/dist/esm/security/ip.validator.js.map +1 -0
- package/dist/esm/security/ssrf.guard.js +176 -0
- package/dist/esm/security/ssrf.guard.js.map +1 -0
- package/dist/esm/security/url.validator.js +166 -0
- package/dist/esm/security/url.validator.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/esm/types/adapter.js +5 -0
- package/dist/esm/types/adapter.js.map +1 -0
- package/dist/esm/types/client.js +5 -0
- package/dist/esm/types/client.js.map +1 -0
- package/dist/esm/types/config.js +5 -0
- package/dist/esm/types/config.js.map +1 -0
- package/dist/esm/types/errors.js +30 -0
- package/dist/esm/types/errors.js.map +1 -0
- package/dist/esm/types/index.js +19 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/types/ip.js +5 -0
- package/dist/esm/types/ip.js.map +1 -0
- package/dist/esm/types/logger.js +5 -0
- package/dist/esm/types/logger.js.map +1 -0
- package/dist/esm/types/request_response.js +5 -0
- package/dist/esm/types/request_response.js.map +1 -0
- package/dist/esm/types/security.js +5 -0
- package/dist/esm/types/security.js.map +1 -0
- package/dist/esm/types/trace.js +11 -0
- package/dist/esm/types/trace.js.map +1 -0
- package/dist/esm/utils/common.js +27 -0
- package/dist/esm/utils/common.js.map +1 -0
- package/dist/esm/utils/domain.js +71 -0
- package/dist/esm/utils/domain.js.map +1 -0
- package/dist/esm/utils/index.js +7 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/ip.range.js +187 -0
- package/dist/esm/utils/ip.range.js.map +1 -0
- package/dist/esm/utils/trace.context.js +199 -0
- package/dist/esm/utils/trace.context.js.map +1 -0
- package/dist/types/adapter/axios.adapter.d.ts +51 -0
- package/dist/types/adapter/axios.adapter.d.ts.map +1 -0
- package/dist/types/adapter/base.d.ts +56 -0
- package/dist/types/adapter/base.d.ts.map +1 -0
- package/dist/types/adapter/index.d.ts +4 -0
- package/dist/types/adapter/index.d.ts.map +1 -0
- package/dist/types/adapter/undici.adapter.d.ts +68 -0
- package/dist/types/adapter/undici.adapter.d.ts.map +1 -0
- package/dist/types/client.d.ts +105 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/config.d.ts +14 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/const/config.const.d.ts +23 -0
- package/dist/types/const/config.const.d.ts.map +1 -0
- package/dist/types/const/dns.const.d.ts +13 -0
- package/dist/types/const/dns.const.d.ts.map +1 -0
- package/dist/types/const/enum.const.d.ts +17 -0
- package/dist/types/const/enum.const.d.ts.map +1 -0
- package/dist/types/const/index.d.ts +5 -0
- package/dist/types/const/index.d.ts.map +1 -0
- package/dist/types/const/ip.const.d.ts +42 -0
- package/dist/types/const/ip.const.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logger/base.d.ts +42 -0
- package/dist/types/logger/base.d.ts.map +1 -0
- package/dist/types/logger/http_client.logger.d.ts +49 -0
- package/dist/types/logger/http_client.logger.d.ts.map +1 -0
- package/dist/types/logger/index.d.ts +4 -0
- package/dist/types/logger/index.d.ts.map +1 -0
- package/dist/types/logger/log.filter.d.ts +56 -0
- package/dist/types/logger/log.filter.d.ts.map +1 -0
- package/dist/types/security/dns.validator.d.ts +61 -0
- package/dist/types/security/dns.validator.d.ts.map +1 -0
- package/dist/types/security/index.d.ts +5 -0
- package/dist/types/security/index.d.ts.map +1 -0
- package/dist/types/security/ip.validator.d.ts +31 -0
- package/dist/types/security/ip.validator.d.ts.map +1 -0
- package/dist/types/security/ssrf.guard.d.ts +54 -0
- package/dist/types/security/ssrf.guard.d.ts.map +1 -0
- package/dist/types/security/url.validator.d.ts +76 -0
- package/dist/types/security/url.validator.d.ts.map +1 -0
- package/dist/types/types/adapter.d.ts +30 -0
- package/dist/types/types/adapter.d.ts.map +1 -0
- package/dist/types/types/client.d.ts +85 -0
- package/dist/types/types/client.d.ts.map +1 -0
- package/dist/types/types/config.d.ts +99 -0
- package/dist/types/types/config.d.ts.map +1 -0
- package/dist/types/types/errors.d.ts +23 -0
- package/dist/types/types/errors.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +10 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/ip.d.ts +32 -0
- package/dist/types/types/ip.d.ts.map +1 -0
- package/dist/types/types/logger.d.ts +136 -0
- package/dist/types/types/logger.d.ts.map +1 -0
- package/dist/types/types/request_response.d.ts +54 -0
- package/dist/types/types/request_response.d.ts.map +1 -0
- package/dist/types/types/security.d.ts +115 -0
- package/dist/types/types/security.d.ts.map +1 -0
- package/dist/types/types/trace.d.ts +34 -0
- package/dist/types/types/trace.d.ts.map +1 -0
- package/dist/types/utils/common.d.ts +14 -0
- package/dist/types/utils/common.d.ts.map +1 -0
- package/dist/types/utils/domain.d.ts +39 -0
- package/dist/types/utils/domain.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/ip.range.d.ts +61 -0
- package/dist/types/utils/ip.range.d.ts.map +1 -0
- package/dist/types/utils/trace.context.d.ts +106 -0
- package/dist/types/utils/trace.context.d.ts.map +1 -0
- package/docs/adapters.md +53 -0
- package/docs/configuration.md +149 -0
- package/docs/logging.md +70 -0
- package/docs/proxy.md +44 -0
- package/docs/security.md +56 -0
- package/docs/trace-context.md +436 -0
- package/package.json +50 -0
- package/src/adapter/axios.adapter.ts +228 -0
- package/src/adapter/base.ts +180 -0
- package/src/adapter/index.ts +3 -0
- package/src/adapter/undici.adapter.ts +282 -0
- package/src/client.ts +552 -0
- package/src/config.ts +86 -0
- package/src/const/config.const.ts +60 -0
- package/src/const/dns.const.ts +15 -0
- package/src/const/enum.const.ts +17 -0
- package/src/const/index.ts +4 -0
- package/src/const/ip.const.ts +139 -0
- package/src/index.ts +8 -0
- package/src/logger/base.ts +75 -0
- package/src/logger/http_client.logger.ts +272 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/log.filter.ts +149 -0
- package/src/security/dns.validator.ts +170 -0
- package/src/security/index.ts +4 -0
- package/src/security/ip.validator.ts +124 -0
- package/src/security/ssrf.guard.ts +224 -0
- package/src/security/url.validator.ts +192 -0
- package/src/types/adapter.ts +38 -0
- package/src/types/client.ts +119 -0
- package/src/types/config.ts +110 -0
- package/src/types/errors.ts +38 -0
- package/src/types/index.ts +27 -0
- package/src/types/ip.ts +34 -0
- package/src/types/logger.ts +150 -0
- package/src/types/request_response.ts +65 -0
- package/src/types/security.ts +126 -0
- package/src/types/trace.ts +35 -0
- package/src/utils/common.ts +28 -0
- package/src/utils/domain.ts +78 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/ip.range.ts +218 -0
- package/src/utils/trace.context.ts +240 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DNS 验证器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { promises as dns } from 'node:dns';
|
|
6
|
+
import { LRUCache } from 'lru-cache';
|
|
7
|
+
import { CommonUtils } from '../utils';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_DNS_CACHE_TTL,
|
|
10
|
+
DEFAULT_DNS_RESOLVE_TIMEOUT,
|
|
11
|
+
DEFAULT_DNS_CACHE_MAX_SIZE
|
|
12
|
+
} from '../const';
|
|
13
|
+
import type {
|
|
14
|
+
DnsResult
|
|
15
|
+
} from '../types';
|
|
16
|
+
import { IpValidator } from './ip.validator';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* DNS 验证器类
|
|
21
|
+
*/
|
|
22
|
+
export class DnsValidator {
|
|
23
|
+
private cache: LRUCache<string, DnsResult>;
|
|
24
|
+
private cacheTtl: number;
|
|
25
|
+
private resolveTimeout: number;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
cacheTtl = DEFAULT_DNS_CACHE_TTL,
|
|
29
|
+
resolveTimeout = DEFAULT_DNS_RESOLVE_TIMEOUT,
|
|
30
|
+
maxCacheSize = DEFAULT_DNS_CACHE_MAX_SIZE
|
|
31
|
+
) {
|
|
32
|
+
this.cacheTtl = cacheTtl;
|
|
33
|
+
this.resolveTimeout = resolveTimeout;
|
|
34
|
+
this.cache = new LRUCache<string, DnsResult>({
|
|
35
|
+
max: maxCacheSize,
|
|
36
|
+
ttl: cacheTtl,
|
|
37
|
+
// 启用 TTL 自动清理
|
|
38
|
+
ttlAutopurge: true,
|
|
39
|
+
// 更新访问时间,实现真正的 LRU
|
|
40
|
+
updateAgeOnGet: true,
|
|
41
|
+
// 更新访问时间时也更新 has() 调用
|
|
42
|
+
updateAgeOnHas: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 解析域名
|
|
48
|
+
* @param hostname 域名
|
|
49
|
+
* @returns DNS 解析结果
|
|
50
|
+
*/
|
|
51
|
+
public async resolve(hostname: string): Promise<DnsResult> {
|
|
52
|
+
// 检查缓存
|
|
53
|
+
const cached = this.cache.get(hostname);
|
|
54
|
+
if (cached) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// 尝试解析 IPv4 和 IPv6
|
|
60
|
+
const addresses: string[] = [];
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const ipv4Addresses = await CommonUtils.withTimeout(dns.resolve4(hostname), this.resolveTimeout, 'DNS resolution timed out');
|
|
64
|
+
addresses.push(...ipv4Addresses);
|
|
65
|
+
} catch {
|
|
66
|
+
// IPv4 解析失败,继续尝试 IPv6
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const ipv6Addresses = await CommonUtils.withTimeout(dns.resolve6(hostname), this.resolveTimeout, 'DNS resolution timed out');
|
|
71
|
+
addresses.push(...ipv6Addresses);
|
|
72
|
+
} catch {
|
|
73
|
+
// IPv6 解析失败
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (addresses.length === 0) {
|
|
77
|
+
throw new Error(`Unable to resolve hostname: ${ hostname }`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result: DnsResult = {
|
|
81
|
+
hostname,
|
|
82
|
+
addresses,
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
ttl: this.cacheTtl
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// 存入缓存,LRUCache 会自动处理 TTL 和容量限制
|
|
88
|
+
this.cache.set(hostname, result);
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`DNS resolution failed for ${ hostname }: ${ error instanceof Error ? error.message : String(error) }`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 解析并验证域名(包括 IP 安全检查)
|
|
101
|
+
* @param hostname 域名
|
|
102
|
+
* @param allowPrivateIp 是否允许私有 IP
|
|
103
|
+
* @returns DNS 解析结果和验证结果
|
|
104
|
+
*/
|
|
105
|
+
public async resolveAndValidate(
|
|
106
|
+
hostname: string,
|
|
107
|
+
allowPrivateIp = false
|
|
108
|
+
): Promise<{ dnsResult: DnsResult, allValid: boolean, invalidIps: string[] }> {
|
|
109
|
+
const dnsResult = await this.resolve(hostname);
|
|
110
|
+
const validationResults = IpValidator.validateMany(dnsResult.addresses, allowPrivateIp);
|
|
111
|
+
|
|
112
|
+
const invalidIps = validationResults
|
|
113
|
+
.filter(result => !result.valid)
|
|
114
|
+
.map(result => result.ip);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
dnsResult,
|
|
118
|
+
allValid: invalidIps.length === 0,
|
|
119
|
+
invalidIps
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 清空缓存
|
|
125
|
+
*/
|
|
126
|
+
public clearCache(): void {
|
|
127
|
+
this.cache.clear();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 清除指定域名的缓存
|
|
132
|
+
* @param hostname 域名
|
|
133
|
+
*/
|
|
134
|
+
public clearCacheFor(hostname: string): void {
|
|
135
|
+
this.cache.delete(hostname);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 获取缓存大小
|
|
140
|
+
* @returns 缓存条目数量
|
|
141
|
+
*/
|
|
142
|
+
public getCacheSize(): number {
|
|
143
|
+
return this.cache.size;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 清理过期的缓存条目
|
|
148
|
+
* LRUCache 会自动清理过期条目(ttlAutopurge: true),此方法主动触发清理
|
|
149
|
+
*/
|
|
150
|
+
public cleanExpiredCache(): void {
|
|
151
|
+
this.cache.purgeStale();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 获取缓存的最大容量
|
|
156
|
+
* @returns 最大缓存条目数量
|
|
157
|
+
*/
|
|
158
|
+
public getMaxCacheSize(): number {
|
|
159
|
+
return this.cache.max;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 检查缓存中是否存在指定域名
|
|
164
|
+
* @param hostname 域名
|
|
165
|
+
* @returns 是否存在
|
|
166
|
+
*/
|
|
167
|
+
public hasCache(hostname: string): boolean {
|
|
168
|
+
return this.cache.has(hostname);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP 地址验证器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
isIPv4,
|
|
7
|
+
isIPv6
|
|
8
|
+
} from 'node:net';
|
|
9
|
+
import type { IpValidationResult } from '../types';
|
|
10
|
+
import { IpRangeUtils } from '../utils';
|
|
11
|
+
import {
|
|
12
|
+
COMPILED_IPV4_RANGES,
|
|
13
|
+
COMPILED_IPV6_RANGES
|
|
14
|
+
} from '../const';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* IP 地址验证器类
|
|
19
|
+
*/
|
|
20
|
+
export class IpValidator {
|
|
21
|
+
/**
|
|
22
|
+
* 验证 IP 地址是否安全
|
|
23
|
+
* @param ip IP 地址
|
|
24
|
+
* @param allowPrivateIp 是否允许私有 IP
|
|
25
|
+
* @returns 验证结果
|
|
26
|
+
*/
|
|
27
|
+
public static validate(ip: string, allowPrivateIp = false): IpValidationResult {
|
|
28
|
+
// 检查是否为有效的 IP 地址
|
|
29
|
+
const isV4 = isIPv4(ip);
|
|
30
|
+
const isV6 = isIPv6(ip);
|
|
31
|
+
|
|
32
|
+
if (!isV4 && !isV6) {
|
|
33
|
+
return {
|
|
34
|
+
valid: false,
|
|
35
|
+
ip,
|
|
36
|
+
isPrivate: false,
|
|
37
|
+
isLoopback: false,
|
|
38
|
+
isLinkLocal: false,
|
|
39
|
+
isMulticast: false,
|
|
40
|
+
isReserved: false,
|
|
41
|
+
reason: 'Invalid IP address format'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 使用预编译的范围进行高性能检查
|
|
46
|
+
let isPrivate: boolean;
|
|
47
|
+
let isLoopback: boolean;
|
|
48
|
+
let isLinkLocal: boolean;
|
|
49
|
+
let isMulticast: boolean;
|
|
50
|
+
let isReserved: boolean;
|
|
51
|
+
|
|
52
|
+
if (isV4) {
|
|
53
|
+
isPrivate = IpRangeUtils.isIpv4InAnyCompiledRange(ip, COMPILED_IPV4_RANGES.private);
|
|
54
|
+
isLoopback = IpRangeUtils.isIpv4InAnyCompiledRange(ip, COMPILED_IPV4_RANGES.loopback);
|
|
55
|
+
isLinkLocal = IpRangeUtils.isIpv4InAnyCompiledRange(ip, COMPILED_IPV4_RANGES.linkLocal);
|
|
56
|
+
isMulticast = IpRangeUtils.isIpv4InAnyCompiledRange(ip, COMPILED_IPV4_RANGES.multicast);
|
|
57
|
+
isReserved = IpRangeUtils.isIpv4InAnyCompiledRange(ip, COMPILED_IPV4_RANGES.reserved);
|
|
58
|
+
} else {
|
|
59
|
+
isPrivate = IpRangeUtils.isIpv6InAnyCompiledRange(ip, COMPILED_IPV6_RANGES.private);
|
|
60
|
+
isLoopback = IpRangeUtils.isIpv6InAnyCompiledRange(ip, COMPILED_IPV6_RANGES.loopback);
|
|
61
|
+
isLinkLocal = IpRangeUtils.isIpv6InAnyCompiledRange(ip, COMPILED_IPV6_RANGES.linkLocal);
|
|
62
|
+
isMulticast = IpRangeUtils.isIpv6InAnyCompiledRange(ip, COMPILED_IPV6_RANGES.multicast);
|
|
63
|
+
isReserved = IpRangeUtils.isIpv6InAnyCompiledRange(ip, COMPILED_IPV6_RANGES.reserved);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 基础验证信息
|
|
67
|
+
const baseResult = {
|
|
68
|
+
ip,
|
|
69
|
+
isPrivate,
|
|
70
|
+
isLoopback,
|
|
71
|
+
isLinkLocal,
|
|
72
|
+
isMulticast,
|
|
73
|
+
isReserved
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 检查是否为受限 IP - 按优先级顺序检查
|
|
77
|
+
const restrictions = [
|
|
78
|
+
{ condition: isLoopback, reason: 'Loopback address is not allowed' },
|
|
79
|
+
{ condition: isLinkLocal, reason: 'Link-local address is not allowed' },
|
|
80
|
+
{ condition: isMulticast, reason: 'Multicast address is not allowed' },
|
|
81
|
+
{ condition: isReserved, reason: 'Reserved address is not allowed' },
|
|
82
|
+
{ condition: isPrivate && !allowPrivateIp, reason: 'Private IP address is not allowed' }
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const { condition, reason } of restrictions) {
|
|
86
|
+
if (condition) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
...baseResult,
|
|
90
|
+
reason
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
valid: true,
|
|
97
|
+
...baseResult
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 批量验证 IP 地址
|
|
103
|
+
* @param ips IP 地址列表
|
|
104
|
+
* @param allowPrivateIp 是否允许私有 IP
|
|
105
|
+
* @returns 验证结果列表
|
|
106
|
+
*/
|
|
107
|
+
public static validateMany(
|
|
108
|
+
ips: string[],
|
|
109
|
+
allowPrivateIp = false
|
|
110
|
+
): IpValidationResult[] {
|
|
111
|
+
return ips.map(ip => this.validate(ip, allowPrivateIp));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 检查是否所有 IP 地址都有效
|
|
116
|
+
* @param ips IP 地址列表
|
|
117
|
+
* @param allowPrivateIp 是否允许私有 IP
|
|
118
|
+
* @returns 是否所有 IP 都有效
|
|
119
|
+
*/
|
|
120
|
+
public static validateAll(ips: string[], allowPrivateIp = false): boolean {
|
|
121
|
+
return this.validateMany(ips, allowPrivateIp).every(result => result.valid);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSRF 防护
|
|
3
|
+
* 整合 IP、DNS 和 URL 验证,防止服务器端请求伪造攻击
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { URL } from 'node:url';
|
|
7
|
+
import { isIP } from 'node:net';
|
|
8
|
+
import type {
|
|
9
|
+
ISsrfGuard,
|
|
10
|
+
SecurityConfig,
|
|
11
|
+
SsrfGuardResult,
|
|
12
|
+
} from '../types';
|
|
13
|
+
import { SecurityError } from '../types';
|
|
14
|
+
import { DnsValidator } from './dns.validator';
|
|
15
|
+
import { IpValidator } from './ip.validator';
|
|
16
|
+
import { UrlValidator } from './url.validator';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SSRF 防护类
|
|
20
|
+
*/
|
|
21
|
+
export class SsrfGuard implements ISsrfGuard {
|
|
22
|
+
private dnsValidator: DnsValidator;
|
|
23
|
+
private urlValidator: UrlValidator;
|
|
24
|
+
private config: SecurityConfig;
|
|
25
|
+
|
|
26
|
+
constructor(config: SecurityConfig) {
|
|
27
|
+
this.config = config;
|
|
28
|
+
this.dnsValidator = new DnsValidator(
|
|
29
|
+
config.dnsCacheTtl,
|
|
30
|
+
undefined, // 使用默认的 DNS 解析超时
|
|
31
|
+
config.dnsCacheMaxSize
|
|
32
|
+
);
|
|
33
|
+
this.urlValidator = new UrlValidator(config.whitelist, config.blacklist);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 验证 URL 是否安全
|
|
38
|
+
* @param url 要验证的 URL
|
|
39
|
+
* @returns 验证结果
|
|
40
|
+
*/
|
|
41
|
+
public async validate(url: string): Promise<SsrfGuardResult> {
|
|
42
|
+
let parsedUrl: URL;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
parsedUrl = new URL(url);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
passed: false,
|
|
49
|
+
originalUrl: url,
|
|
50
|
+
hostname: '',
|
|
51
|
+
reason: 'Invalid URL format'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const hostname = parsedUrl.hostname;
|
|
56
|
+
|
|
57
|
+
// 第一步:URL 验证(黑白名单)
|
|
58
|
+
const urlValidation = this.urlValidator.validate(url);
|
|
59
|
+
if (!urlValidation.valid) {
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
originalUrl: url,
|
|
63
|
+
hostname,
|
|
64
|
+
reason: urlValidation.reason
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 第二步:检查是否为 IP 地址
|
|
69
|
+
const ipVersion = isIP(hostname);
|
|
70
|
+
if (ipVersion !== 0) {
|
|
71
|
+
// 直接使用 IP 地址,需要验证 IP 安全性
|
|
72
|
+
if (!this.config.enableIpValidation) {
|
|
73
|
+
return {
|
|
74
|
+
passed: true,
|
|
75
|
+
originalUrl: url,
|
|
76
|
+
hostname,
|
|
77
|
+
resolvedIps: [hostname],
|
|
78
|
+
safeIp: hostname
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const ipValidation = IpValidator.validate(hostname, this.config.allowPrivateIp);
|
|
83
|
+
if (!ipValidation.valid) {
|
|
84
|
+
return {
|
|
85
|
+
passed: false,
|
|
86
|
+
originalUrl: url,
|
|
87
|
+
hostname,
|
|
88
|
+
resolvedIps: [hostname],
|
|
89
|
+
reason: ipValidation.reason
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
passed: true,
|
|
95
|
+
originalUrl: url,
|
|
96
|
+
hostname,
|
|
97
|
+
resolvedIps: [hostname],
|
|
98
|
+
safeIp: hostname
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 第三步:DNS 解析和验证
|
|
103
|
+
if (!this.config.enableDnsValidation) {
|
|
104
|
+
return {
|
|
105
|
+
passed: true,
|
|
106
|
+
originalUrl: url,
|
|
107
|
+
hostname
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const { dnsResult, allValid, invalidIps } = await this.dnsValidator.resolveAndValidate(
|
|
113
|
+
hostname,
|
|
114
|
+
this.config.allowPrivateIp
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (!allValid) {
|
|
118
|
+
return {
|
|
119
|
+
passed: false,
|
|
120
|
+
originalUrl: url,
|
|
121
|
+
hostname,
|
|
122
|
+
resolvedIps: dnsResult.addresses,
|
|
123
|
+
reason: `DNS resolved to unsafe IP addresses: ${ invalidIps.join(', ') }`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 选择第一个有效的 IP 地址
|
|
128
|
+
const safeIp = dnsResult.addresses[0];
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
passed: true,
|
|
132
|
+
originalUrl: url,
|
|
133
|
+
hostname,
|
|
134
|
+
resolvedIps: dnsResult.addresses,
|
|
135
|
+
safeIp
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
passed: false,
|
|
140
|
+
originalUrl: url,
|
|
141
|
+
hostname,
|
|
142
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 验证并返回安全的 URL
|
|
149
|
+
* @param url 要验证的 URL
|
|
150
|
+
* @returns 安全的 URL 和验证结果
|
|
151
|
+
* @throws {SecurityError} 验证失败时抛出错误
|
|
152
|
+
*/
|
|
153
|
+
public async validateAndGetSafeUrl(url: string): Promise<{ safeUrl: string, result: SsrfGuardResult }> {
|
|
154
|
+
const result = await this.validate(url);
|
|
155
|
+
|
|
156
|
+
if (!result.passed) {
|
|
157
|
+
throw new SecurityError(
|
|
158
|
+
result.reason || 'URL validation failed',
|
|
159
|
+
'SSRF_VALIDATION_FAILED',
|
|
160
|
+
url
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
safeUrl: url,
|
|
166
|
+
result
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 更新配置
|
|
172
|
+
* @param config 新的安全配置
|
|
173
|
+
*/
|
|
174
|
+
public updateConfig(config: Partial<SecurityConfig>): void {
|
|
175
|
+
this.config = { ...this.config, ...config };
|
|
176
|
+
|
|
177
|
+
// 如果 DNS 缓存配置变更,需要重新创建 DNS 验证器
|
|
178
|
+
if (config.dnsCacheTtl !== undefined || config.dnsCacheMaxSize !== undefined) {
|
|
179
|
+
this.dnsValidator = new DnsValidator(
|
|
180
|
+
this.config.dnsCacheTtl,
|
|
181
|
+
undefined,
|
|
182
|
+
this.config.dnsCacheMaxSize
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (config.whitelist !== undefined || config.blacklist !== undefined) {
|
|
187
|
+
this.urlValidator = new UrlValidator(
|
|
188
|
+
config.whitelist ?? this.config.whitelist,
|
|
189
|
+
config.blacklist ?? this.config.blacklist
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 清空 DNS 缓存
|
|
196
|
+
*/
|
|
197
|
+
public clearDnsCache(): void {
|
|
198
|
+
this.dnsValidator.clearCache();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 获取 DNS 缓存大小
|
|
203
|
+
* @returns 缓存条目数量
|
|
204
|
+
*/
|
|
205
|
+
public getDnsCacheSize(): number {
|
|
206
|
+
return this.dnsValidator.getCacheSize();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 清理过期的 DNS 缓存
|
|
211
|
+
*/
|
|
212
|
+
public cleanExpiredDnsCache(): void {
|
|
213
|
+
this.dnsValidator.cleanExpiredCache();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 获取当前配置
|
|
218
|
+
* @returns 安全配置副本
|
|
219
|
+
*/
|
|
220
|
+
public getConfig(): SecurityConfig {
|
|
221
|
+
return { ...this.config };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL 验证器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { URL } from 'node:url';
|
|
6
|
+
import type { UrlValidationResult } from '../types';
|
|
7
|
+
import { DomainUtils } from '../utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* URL 验证器类
|
|
11
|
+
*/
|
|
12
|
+
export class UrlValidator {
|
|
13
|
+
private whitelist: string[];
|
|
14
|
+
private blacklist: string[];
|
|
15
|
+
private whitelistRegexps: RegExp[];
|
|
16
|
+
private blacklistRegexps: RegExp[];
|
|
17
|
+
|
|
18
|
+
constructor(whitelist: string[] = [], blacklist: string[] = []) {
|
|
19
|
+
this.whitelist = whitelist;
|
|
20
|
+
this.blacklist = blacklist;
|
|
21
|
+
// 预生成正则表达式,提高匹配性能和安全性
|
|
22
|
+
this.whitelistRegexps = DomainUtils.createDomainMatchers(whitelist);
|
|
23
|
+
this.blacklistRegexps = DomainUtils.createDomainMatchers(blacklist);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 验证 URL
|
|
28
|
+
* @param url URL 字符串
|
|
29
|
+
* @returns 验证结果
|
|
30
|
+
*/
|
|
31
|
+
public validate(url: string): UrlValidationResult {
|
|
32
|
+
let parsedUrl: URL;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
parsedUrl = new URL(url);
|
|
36
|
+
} catch {
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
url,
|
|
40
|
+
inWhitelist: false,
|
|
41
|
+
inBlacklist: false,
|
|
42
|
+
reason: 'Invalid URL format'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 只允许 http 和 https 协议
|
|
47
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
url,
|
|
51
|
+
inWhitelist: false,
|
|
52
|
+
inBlacklist: false,
|
|
53
|
+
reason: `Protocol ${ parsedUrl.protocol } is not allowed. Only http and https are supported.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hostname = parsedUrl.hostname;
|
|
58
|
+
|
|
59
|
+
// 检查白名单(使用预生成的正则表达式)
|
|
60
|
+
const inWhitelist = this.matchesAny(hostname, this.whitelistRegexps);
|
|
61
|
+
// 检查黑名单(使用预生成的正则表达式)
|
|
62
|
+
const inBlacklist = this.matchesAny(hostname, this.blacklistRegexps);
|
|
63
|
+
|
|
64
|
+
// 配了白名单, 优先使用白名单模式, 在名单内的域名通过, 其余拒绝.
|
|
65
|
+
// 同时配有黑名单, 黑名单辅助拒绝其余通过的域名.
|
|
66
|
+
if (this.whitelist.length > 0 && !inWhitelist) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
url,
|
|
70
|
+
inWhitelist,
|
|
71
|
+
inBlacklist,
|
|
72
|
+
reason: 'Domain is not in whitelist'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 配了黑名单, 使用黑名单模式, 在黑名单内的域名拒绝, 其余通过.
|
|
77
|
+
if (this.blacklist.length > 0 && inBlacklist) {
|
|
78
|
+
return {
|
|
79
|
+
valid: false,
|
|
80
|
+
url,
|
|
81
|
+
inWhitelist,
|
|
82
|
+
inBlacklist,
|
|
83
|
+
reason: 'Domain is in blacklist'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 黑白名单没配的, 默认通过.
|
|
88
|
+
return {
|
|
89
|
+
valid: true,
|
|
90
|
+
url,
|
|
91
|
+
inWhitelist,
|
|
92
|
+
inBlacklist
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 检查域名是否匹配任一模式(使用预生成的正则表达式)
|
|
98
|
+
* @param hostname 域名
|
|
99
|
+
* @param regexps 正则表达式列表
|
|
100
|
+
* @returns 是否匹配
|
|
101
|
+
*/
|
|
102
|
+
private matchesAny(hostname: string, regexps: RegExp[]): boolean {
|
|
103
|
+
return regexps.some(regex => regex.test(hostname));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 更新白名单
|
|
108
|
+
* @param whitelist 新的白名单
|
|
109
|
+
*/
|
|
110
|
+
public setWhitelist(whitelist: string[]): void {
|
|
111
|
+
this.whitelist = whitelist;
|
|
112
|
+
this.whitelistRegexps = DomainUtils.createDomainMatchers(whitelist);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 更新黑名单
|
|
117
|
+
* @param blacklist 新的黑名单
|
|
118
|
+
*/
|
|
119
|
+
public setBlacklist(blacklist: string[]): void {
|
|
120
|
+
this.blacklist = blacklist;
|
|
121
|
+
this.blacklistRegexps = DomainUtils.createDomainMatchers(blacklist);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 添加到白名单
|
|
126
|
+
* @param patterns 要添加的模式
|
|
127
|
+
*/
|
|
128
|
+
public addToWhitelist(...patterns: string[]): void {
|
|
129
|
+
this.whitelist.push(...patterns);
|
|
130
|
+
this.whitelistRegexps = DomainUtils.createDomainMatchers(this.whitelist);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 添加到黑名单
|
|
135
|
+
* @param patterns 要添加的模式
|
|
136
|
+
*/
|
|
137
|
+
public addToBlacklist(...patterns: string[]): void {
|
|
138
|
+
this.blacklist.push(...patterns);
|
|
139
|
+
this.blacklistRegexps = DomainUtils.createDomainMatchers(this.blacklist);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 从白名单移除
|
|
144
|
+
* @param patterns 要移除的模式
|
|
145
|
+
*/
|
|
146
|
+
public removeFromWhitelist(...patterns: string[]): void {
|
|
147
|
+
this.whitelist = this.whitelist.filter(p => !patterns.includes(p));
|
|
148
|
+
this.whitelistRegexps = DomainUtils.createDomainMatchers(this.whitelist);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 从黑名单移除
|
|
153
|
+
* @param patterns 要移除的模式
|
|
154
|
+
*/
|
|
155
|
+
public removeFromBlacklist(...patterns: string[]): void {
|
|
156
|
+
this.blacklist = this.blacklist.filter(p => !patterns.includes(p));
|
|
157
|
+
this.blacklistRegexps = DomainUtils.createDomainMatchers(this.blacklist);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取白名单
|
|
162
|
+
* @returns 白名单副本
|
|
163
|
+
*/
|
|
164
|
+
public getWhitelist(): string[] {
|
|
165
|
+
return [...this.whitelist];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 获取黑名单
|
|
170
|
+
* @returns 黑名单副本
|
|
171
|
+
*/
|
|
172
|
+
public getBlacklist(): string[] {
|
|
173
|
+
return [...this.blacklist];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 清空白名单
|
|
178
|
+
*/
|
|
179
|
+
public clearWhitelist(): void {
|
|
180
|
+
this.whitelist = [];
|
|
181
|
+
this.whitelistRegexps = [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 清空黑名单
|
|
186
|
+
*/
|
|
187
|
+
public clearBlacklist(): void {
|
|
188
|
+
this.blacklist = [];
|
|
189
|
+
this.blacklistRegexps = [];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|