mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/mihomo-party-org/clash-party.git
synced 2026-09-20 08:03:39 +08:00
refactor: streamline validation utilities
This commit is contained in:
@@ -1,32 +1,34 @@
|
||||
import validator from 'validator'
|
||||
|
||||
const domainValidator = (value: string): boolean => {
|
||||
if (value.length > 253 || value.length < 2) return false
|
||||
|
||||
// 检查是否为合法的 FQDN (完全限定域名)
|
||||
if (validator.isFQDN(value, { require_tld: true })) return true
|
||||
|
||||
// 允许特殊的本地域名
|
||||
return ['localhost', 'local', 'localdomain'].includes(value.toLowerCase())
|
||||
export interface ValidationResult {
|
||||
ok: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
const domainSuffixValidator = (value: string): boolean => {
|
||||
// 域名后缀验证 - 可以是完整域名或带通配符的域名后缀
|
||||
return validator.isFQDN(value, { require_tld: true, allow_wildcard: true })
|
||||
}
|
||||
type BooleanValidator = (value: string) => boolean
|
||||
|
||||
const domainKeywordValidator = (value: string): boolean => {
|
||||
// 域名关键字不能包含逗号和空格
|
||||
return (
|
||||
value.length > 0 &&
|
||||
validator.isWhitelisted(
|
||||
value,
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._'
|
||||
)
|
||||
)
|
||||
}
|
||||
const LOCAL_DOMAINS = new Set(['localhost', 'local', 'localdomain'])
|
||||
const NETWORK_TYPES = new Set(['tcp', 'udp'])
|
||||
const LOCAL_BYPASS_PLATFORMS = new Set<string>(['win32', 'darwin'])
|
||||
const INBOUND_TYPES = new Set([
|
||||
'http',
|
||||
'https',
|
||||
'socks',
|
||||
'socks4',
|
||||
'socks5',
|
||||
'tproxy',
|
||||
'redir',
|
||||
'mixed'
|
||||
])
|
||||
|
||||
const domainRegexValidator = (value: string): boolean => {
|
||||
const DOMAIN_KEYWORD_PATTERN = /^[a-zA-Z0-9._-]+$/
|
||||
const DOMAIN_WILDCARD_PATTERN = /^[a-zA-Z0-9.*?-]+$/
|
||||
const WINDOWS_PATH_PATTERN = /^[a-zA-Z]:[\\/].+/
|
||||
const UNIX_PATH_PATTERN = /^\/.+/
|
||||
const ANDROID_PACKAGE_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i
|
||||
const PROCESS_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/
|
||||
|
||||
const isValidRegex: BooleanValidator = (value) => {
|
||||
try {
|
||||
new RegExp(value)
|
||||
return true
|
||||
@@ -35,62 +37,82 @@ const domainRegexValidator = (value: string): boolean => {
|
||||
}
|
||||
}
|
||||
|
||||
const portValidator = (value: string): boolean => {
|
||||
return validator.isPort(value)
|
||||
}
|
||||
const isNamedIdentifier = (value: string, ignore = '-_'): boolean =>
|
||||
value.length > 0 && validator.isAlphanumeric(value, 'en-US', { ignore })
|
||||
|
||||
const ipv4CIDRValidator = (value: string): boolean => {
|
||||
// 验证 IPv4 CIDR 格式 (例如:192.168.1.0/24)
|
||||
if (!value.includes('/')) return false
|
||||
const replaceWildcards = (value: string): string => value.replace(/\*/g, 'a').replace(/\?/g, 'a')
|
||||
|
||||
const [ip, cidr] = value.split('/')
|
||||
const cidrNum = parseInt(cidr, 10)
|
||||
const isValidHostname = (host: string): boolean =>
|
||||
validator.isFQDN(host, { require_tld: false }) ||
|
||||
validator.isAlphanumeric(host, 'en-US', { ignore: '-.' })
|
||||
|
||||
return validator.isIP(ip, 4) && !isNaN(cidrNum) && cidrNum >= 0 && cidrNum <= 32
|
||||
}
|
||||
const validationResult = (ok: boolean, error: string): ValidationResult =>
|
||||
ok ? { ok: true } : { ok: false, error }
|
||||
|
||||
const ipv6CIDRValidator = (value: string): boolean => {
|
||||
// 验证 IPv6 CIDR 格式 (例如:2001:db8::/32)
|
||||
if (!value.includes('/')) return false
|
||||
const integerValidator =
|
||||
(min: number, max: number): BooleanValidator =>
|
||||
(value) =>
|
||||
validator.isInt(value, { min, max })
|
||||
|
||||
const [ip, cidr] = value.split('/')
|
||||
const cidrNum = parseInt(cidr, 10)
|
||||
|
||||
return validator.isIP(ip, 6) && !isNaN(cidrNum) && cidrNum >= 0 && cidrNum <= 128
|
||||
}
|
||||
|
||||
// 便捷函数:将 ValidationResult 转换为布尔值
|
||||
export const isValid = (result: ValidationResult): boolean => result.ok
|
||||
|
||||
// 便捷函数:获取错误信息
|
||||
export const getError = (result: ValidationResult): string | undefined => result.error
|
||||
|
||||
// IP CIDR 验证器(同时支持 IPv4 和 IPv6)
|
||||
const ipCIDRValidator = (value: string): boolean => {
|
||||
return ipv4CIDRValidator(value) || ipv6CIDRValidator(value)
|
||||
// Domain rules
|
||||
|
||||
export const domainValidator: BooleanValidator = (value) => {
|
||||
if (value.length < 2 || value.length > 253) return false
|
||||
|
||||
return validator.isFQDN(value, { require_tld: true }) || LOCAL_DOMAINS.has(value.toLowerCase())
|
||||
}
|
||||
|
||||
const sysProxyBypassValidator = (
|
||||
export const domainSuffixValidator: BooleanValidator = (value) =>
|
||||
validator.isFQDN(value, { require_tld: true, allow_wildcard: true })
|
||||
|
||||
export const domainKeywordValidator: BooleanValidator = (value) =>
|
||||
DOMAIN_KEYWORD_PATTERN.test(value)
|
||||
|
||||
export const domainRegexValidator = isValidRegex
|
||||
|
||||
export const domainWildcardValidator: BooleanValidator = (value) => {
|
||||
if (!DOMAIN_WILDCARD_PATTERN.test(value)) return false
|
||||
|
||||
const normalizedDomain = replaceWildcards(value)
|
||||
return (
|
||||
normalizedDomain.includes('.') && validator.isFQDN(normalizedDomain, { require_tld: false })
|
||||
)
|
||||
}
|
||||
|
||||
// Network rules
|
||||
|
||||
export const portValidator: BooleanValidator = (value) => validator.isPort(value)
|
||||
|
||||
export const portRangeValidator: BooleanValidator = (value) => {
|
||||
const [start, end, ...rest] = value.split('-')
|
||||
|
||||
if (end === undefined) return validator.isPort(start)
|
||||
if (rest.length > 0) return false
|
||||
|
||||
return validator.isPort(start) && validator.isPort(end) && Number(start) <= Number(end)
|
||||
}
|
||||
|
||||
export const ipv4CIDRValidator: BooleanValidator = (value) => validator.isIPRange(value, 4)
|
||||
export const ipv6CIDRValidator: BooleanValidator = (value) => validator.isIPRange(value, 6)
|
||||
export const ipCIDRValidator: BooleanValidator = (value) => validator.isIPRange(value)
|
||||
|
||||
export const sysProxyBypassValidator = (
|
||||
value: string,
|
||||
targetPlatform: NodeJS.Platform | string
|
||||
): boolean => {
|
||||
const entry = value.trim()
|
||||
if (entry === '') return false
|
||||
|
||||
if (!entry) return false
|
||||
if (validator.isIP(entry)) return true
|
||||
|
||||
if (targetPlatform !== 'win32' && validator.isIPRange(entry)) return true
|
||||
|
||||
if (
|
||||
(targetPlatform === 'win32' || targetPlatform === 'darwin') &&
|
||||
entry.toLowerCase() === '<local>'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
const normalizedEntry = entry.toLowerCase()
|
||||
if (LOCAL_BYPASS_PLATFORMS.has(targetPlatform) && normalizedEntry === '<local>') return true
|
||||
|
||||
if (targetPlatform === 'win32' && /[*?]/.test(entry)) {
|
||||
const normalizedPattern = entry.replace(/\*/g, 'wildcard').replace(/\?/g, 'q')
|
||||
return validator.isFQDN(normalizedPattern, {
|
||||
return validator.isFQDN(entry.replace(/\*/g, 'wildcard').replace(/\?/g, 'q'), {
|
||||
require_tld: false,
|
||||
allow_numeric_tld: true
|
||||
})
|
||||
@@ -103,321 +125,106 @@ const sysProxyBypassValidator = (
|
||||
})
|
||||
}
|
||||
|
||||
// DOMAIN-WILDCARD 验证器 - 仅支持 * 和 ? 通配符
|
||||
const domainWildcardValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 检查是否只包含合法字符(字母、数字、点、*、?、-)
|
||||
const validPattern = /^[a-zA-Z0-9.*?-]+$/
|
||||
if (!validPattern.test(value)) return false
|
||||
// 移除通配符后验证基本格式
|
||||
const withoutWildcards = value.replace(/\*/g, 'a').replace(/\?/g, 'a')
|
||||
// 至少要有一个点(域名结构)
|
||||
return withoutWildcards.includes('.')
|
||||
}
|
||||
// Rule values
|
||||
|
||||
// GEOSITE 验证器 - 站点名称验证
|
||||
const geositeValidator = (value: string): boolean => {
|
||||
// GEOSITE 名称只能包含字母、数字、连字符和下划线
|
||||
return validator.isAlphanumeric(value, 'en-US', { ignore: '-_' }) && value.length > 0
|
||||
}
|
||||
export const geositeValidator: BooleanValidator = (value) => isNamedIdentifier(value)
|
||||
export const geoipValidator: BooleanValidator = (value) => validator.isISO31661Alpha2(value)
|
||||
export const asnValidator = integerValidator(1, 4_294_967_295)
|
||||
export const uidValidator = integerValidator(0, 65_535)
|
||||
export const dscpValidator = integerValidator(0, 63)
|
||||
export const networkValidator: BooleanValidator = (value) => NETWORK_TYPES.has(value.toLowerCase())
|
||||
|
||||
// GEOIP 验证器 - 国家代码验证(ISO 3166-1 alpha-2)
|
||||
const geoipValidator = (value: string): boolean => {
|
||||
// 支持 2 位国家代码(大小写不敏感)
|
||||
return validator.isAlpha(value) && value.length === 2
|
||||
}
|
||||
export const inTypeValidator: BooleanValidator = (value) =>
|
||||
value.split('/').every((type) => INBOUND_TYPES.has(type.toLowerCase()))
|
||||
|
||||
// ASN 验证器 - 自治系统号验证
|
||||
const asnValidator = (value: string): boolean => {
|
||||
// ASN 范围:1 - 4294967295 (32-bit)
|
||||
return validator.isInt(value, { min: 1, max: 4294967295 })
|
||||
}
|
||||
export const inUserValidator: BooleanValidator = (value) =>
|
||||
value.length > 0 && value.split('/').every((user) => isNamedIdentifier(user, '-_.'))
|
||||
|
||||
// UID 验证器 - Linux 用户 ID 验证
|
||||
const uidValidator = (value: string): boolean => {
|
||||
// UID 范围:0 - 65535 (大多数系统)
|
||||
return validator.isInt(value, { min: 0, max: 65535 })
|
||||
}
|
||||
export const inNameValidator: BooleanValidator = (value) => isNamedIdentifier(value)
|
||||
export const ruleSetValidator: BooleanValidator = (value) => isNamedIdentifier(value)
|
||||
|
||||
// DSCP 验证器 - 区分服务代码点验证
|
||||
const dscpValidator = (value: string): boolean => {
|
||||
// DSCP 范围:0 - 63 (6-bit)
|
||||
return validator.isInt(value, { min: 0, max: 63 })
|
||||
}
|
||||
// Process rules
|
||||
|
||||
// NETWORK 验证器 - 网络类型验证
|
||||
const networkValidator = (value: string): boolean => {
|
||||
return validator.isIn(value.toLowerCase(), ['tcp', 'udp'])
|
||||
}
|
||||
export const processPathValidator: BooleanValidator = (value) =>
|
||||
WINDOWS_PATH_PATTERN.test(value) ||
|
||||
UNIX_PATH_PATTERN.test(value) ||
|
||||
ANDROID_PACKAGE_PATTERN.test(value)
|
||||
|
||||
// 进程路径验证器
|
||||
const processPathValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// Windows 路径或 Unix 路径
|
||||
const windowsPath = /^[a-zA-Z]:[\\/].+/
|
||||
const unixPath = /^\/.*/
|
||||
const androidPackage = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i
|
||||
return windowsPath.test(value) || unixPath.test(value) || androidPackage.test(value)
|
||||
}
|
||||
export const processPathWildcardValidator: BooleanValidator = (value) =>
|
||||
value.length > 0 && processPathValidator(replaceWildcards(value))
|
||||
|
||||
// 进程路径通配符验证器
|
||||
const processPathWildcardValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 包含通配符的路径,移除通配符后检查路径格式
|
||||
const withoutWildcards = value.replace(/\*/g, 'a').replace(/\?/g, 'a')
|
||||
return processPathValidator(withoutWildcards)
|
||||
}
|
||||
export const processPathRegexValidator = isValidRegex
|
||||
|
||||
// 进程路径正则验证器
|
||||
const processPathRegexValidator = (value: string): boolean => {
|
||||
try {
|
||||
new RegExp(value)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
export const processNameValidator: BooleanValidator = (value) => PROCESS_NAME_PATTERN.test(value)
|
||||
|
||||
// 进程名称验证器
|
||||
const processNameValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 进程名或 Android 包名
|
||||
const processName = /^[a-zA-Z0-9\-_.]+$/
|
||||
const androidPackage = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i
|
||||
return processName.test(value) || androidPackage.test(value)
|
||||
}
|
||||
export const processNameWildcardValidator: BooleanValidator = (value) =>
|
||||
value.length > 0 && processNameValidator(replaceWildcards(value))
|
||||
|
||||
// 进程名称通配符验证器
|
||||
const processNameWildcardValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 移除通配符后检查进程名格式
|
||||
const withoutWildcards = value.replace(/\*/g, 'a').replace(/\?/g, 'a')
|
||||
return processNameValidator(withoutWildcards)
|
||||
}
|
||||
export const processNameRegexValidator = isValidRegex
|
||||
|
||||
// 进程名称正则验证器
|
||||
const processNameRegexValidator = (value: string): boolean => {
|
||||
try {
|
||||
new RegExp(value)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Logical rules
|
||||
|
||||
// IN-TYPE 验证器 - 入站类型验证
|
||||
const inTypeValidator = (value: string): boolean => {
|
||||
// 支持单个或多个类型(用 / 分隔)
|
||||
const types = value.split('/')
|
||||
const validTypes = ['http', 'https', 'socks', 'socks4', 'socks5', 'tproxy', 'redir', 'mixed']
|
||||
return types.length > 0 && types.every((type) => validator.isIn(type.toLowerCase(), validTypes))
|
||||
}
|
||||
export const logicRuleValidator: BooleanValidator = (value) => {
|
||||
if (!value.startsWith('(') || !value.endsWith(')')) return false
|
||||
|
||||
// IN-USER 验证器 - 入站用户名验证
|
||||
const inUserValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 支持多个用户名(用 / 分隔)
|
||||
const users = value.split('/')
|
||||
return users.every(
|
||||
(user) => user.length > 0 && validator.isAlphanumeric(user, 'en-US', { ignore: '-_.' })
|
||||
)
|
||||
}
|
||||
|
||||
// IN-NAME 验证器 - 入站名称验证
|
||||
const inNameValidator = (value: string): boolean => {
|
||||
// 入站名称可以包含字母、数字、连字符和下划线
|
||||
return validator.isAlphanumeric(value, 'en-US', { ignore: '-_' }) && value.length > 0
|
||||
}
|
||||
|
||||
// RULE-SET 验证器 - 规则集名称验证
|
||||
const ruleSetValidator = (value: string): boolean => {
|
||||
// 规则集名称(对应 rule-providers 中定义的名称)
|
||||
return validator.isAlphanumeric(value, 'en-US', { ignore: '-_' }) && value.length > 0
|
||||
}
|
||||
|
||||
// 逻辑规则验证器 - AND, OR, NOT
|
||||
const logicRuleValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 检查括号是否匹配
|
||||
let depth = 0
|
||||
for (const char of value) {
|
||||
if (char === '(') depth++
|
||||
if (char === ')') depth--
|
||||
if (char === '(') depth += 1
|
||||
if (char === ')') depth -= 1
|
||||
if (depth < 0) return false
|
||||
}
|
||||
return depth === 0 && value.startsWith('(') && value.endsWith(')')
|
||||
|
||||
return depth === 0
|
||||
}
|
||||
|
||||
// SUB-RULE 验证器 - 子规则验证
|
||||
const subRuleValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 格式:(RULE_TYPE,payload) 或 provider_name
|
||||
if (value.startsWith('(') && value.endsWith(')')) {
|
||||
return logicRuleValidator(value)
|
||||
}
|
||||
// 如果不是括号格式,则视为 provider 名称
|
||||
return ruleSetValidator(value)
|
||||
export const subRuleValidator: BooleanValidator = (value) => {
|
||||
if (!value) return false
|
||||
return value.startsWith('(') && value.endsWith(')')
|
||||
? logicRuleValidator(value)
|
||||
: ruleSetValidator(value)
|
||||
}
|
||||
|
||||
// 端口范围验证器(支持单个端口或范围)
|
||||
const portRangeValidator = (value: string): boolean => {
|
||||
// 支持单个端口或范围格式,如:80 或 8000-9000
|
||||
if (value.includes('-')) {
|
||||
const [start, end] = value.split('-')
|
||||
return validator.isPort(start) && validator.isPort(end) && parseInt(start) <= parseInt(end)
|
||||
}
|
||||
return validator.isPort(value)
|
||||
}
|
||||
// Structured validation results
|
||||
|
||||
export {
|
||||
domainValidator,
|
||||
domainSuffixValidator,
|
||||
domainKeywordValidator,
|
||||
domainRegexValidator,
|
||||
domainWildcardValidator,
|
||||
geositeValidator,
|
||||
geoipValidator,
|
||||
asnValidator,
|
||||
uidValidator,
|
||||
dscpValidator,
|
||||
networkValidator,
|
||||
processPathValidator,
|
||||
processPathWildcardValidator,
|
||||
processPathRegexValidator,
|
||||
processNameValidator,
|
||||
processNameWildcardValidator,
|
||||
processNameRegexValidator,
|
||||
inTypeValidator,
|
||||
inUserValidator,
|
||||
inNameValidator,
|
||||
ruleSetValidator,
|
||||
logicRuleValidator,
|
||||
subRuleValidator,
|
||||
portValidator,
|
||||
portRangeValidator,
|
||||
ipv4CIDRValidator,
|
||||
ipv6CIDRValidator,
|
||||
ipCIDRValidator,
|
||||
sysProxyBypassValidator
|
||||
}
|
||||
export const isIPv4 = (ip: string): ValidationResult =>
|
||||
validationResult(validator.isIP(ip, 4), '不是有效的 IPv4 地址')
|
||||
|
||||
// 通用验证结果类型
|
||||
export interface ValidationResult {
|
||||
ok: boolean
|
||||
error?: string
|
||||
}
|
||||
export const isIPv6 = (ip: string): ValidationResult =>
|
||||
validationResult(validator.isIP(ip, 6), '不是有效的 IPv6 地址')
|
||||
|
||||
// 验证 IPv4 地址
|
||||
export const isIPv4 = (ip: string): ValidationResult => {
|
||||
if (!validator.isIP(ip, 4)) {
|
||||
return { ok: false, error: '不是有效的 IPv4 地址' }
|
||||
}
|
||||
return { ok: true }
|
||||
}
|
||||
export const isValidPort = (port: string): ValidationResult =>
|
||||
validationResult(validator.isPort(port), '端口号必须在 1-65535 范围内')
|
||||
|
||||
// 验证 IPv6 地址
|
||||
export const isIPv6 = (ip: string): ValidationResult => {
|
||||
if (!validator.isIP(ip, 6)) {
|
||||
return { ok: false, error: '不是有效的 IPv6 地址' }
|
||||
}
|
||||
return { ok: true }
|
||||
}
|
||||
const validateListenAddress = (
|
||||
input: string | undefined,
|
||||
allowUnbracketedIPv6: boolean
|
||||
): ValidationResult => {
|
||||
const value = input?.trim()
|
||||
if (!value) return { ok: true }
|
||||
|
||||
// 验证端口
|
||||
export const isValidPort = (port: string): ValidationResult => {
|
||||
if (!validator.isPort(port)) {
|
||||
return { ok: false, error: '端口号必须在 1-65535 范围内' }
|
||||
}
|
||||
return { ok: true }
|
||||
}
|
||||
if (/^:\d+$/.test(value)) return isValidPort(value.slice(1))
|
||||
|
||||
// 验证监听地址
|
||||
export const isValidListenAddress = (s: string | undefined): ValidationResult => {
|
||||
if (!s || s.trim() === '') return { ok: true }
|
||||
const separatorIndex = value.lastIndexOf(':')
|
||||
if (separatorIndex < 0) return { ok: false, error: '应包含端口号' }
|
||||
|
||||
const v = s.trim()
|
||||
|
||||
// 格式::port (仅端口)
|
||||
if (v.startsWith(':')) {
|
||||
return isValidPort(v.slice(1))
|
||||
}
|
||||
|
||||
const idx = v.lastIndexOf(':')
|
||||
if (idx === -1) return { ok: false, error: '应包含端口号' }
|
||||
|
||||
const host = v.slice(0, idx)
|
||||
const port = v.slice(idx + 1)
|
||||
|
||||
// 验证端口
|
||||
const portResult = isValidPort(port)
|
||||
const host = value.slice(0, separatorIndex)
|
||||
const portResult = isValidPort(value.slice(separatorIndex + 1))
|
||||
if (!portResult.ok) return portResult
|
||||
|
||||
// 格式:[IPv6]:port
|
||||
if (host.startsWith('[') && host.endsWith(']')) {
|
||||
const inner = host.slice(1, -1)
|
||||
return isIPv6(inner)
|
||||
return isIPv6(host.slice(1, -1))
|
||||
}
|
||||
|
||||
// IPv4 地址
|
||||
if (validator.isIP(host, 4)) {
|
||||
return { ok: true }
|
||||
}
|
||||
const validHost =
|
||||
validator.isIP(host, 4) ||
|
||||
(allowUnbracketedIPv6 && validator.isIP(host, 6)) ||
|
||||
isValidHostname(host)
|
||||
|
||||
// 域名或主机名 (使用宽松的 FQDN 验证)
|
||||
if (
|
||||
validator.isFQDN(host, { require_tld: false }) ||
|
||||
validator.isAlphanumeric(host, 'en-US', { ignore: '-.' })
|
||||
) {
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
return { ok: false, error: '主机名包含非法字符' }
|
||||
return validationResult(validHost, '主机名包含非法字符')
|
||||
}
|
||||
|
||||
// 验证监听地址(完整版,包含 0.0.0.0 和 ::)
|
||||
export const isValidListenAddressFull = (s: string | undefined): ValidationResult => {
|
||||
if (!s || s.trim() === '') return { ok: true }
|
||||
export const isValidListenAddress = (input: string | undefined): ValidationResult =>
|
||||
validateListenAddress(input, false)
|
||||
|
||||
const v = s.trim()
|
||||
|
||||
// 格式::port (仅端口)
|
||||
if (v.startsWith(':')) {
|
||||
return isValidPort(v.slice(1))
|
||||
}
|
||||
|
||||
const idx = v.lastIndexOf(':')
|
||||
if (idx === -1) return { ok: false, error: '应包含端口号' }
|
||||
|
||||
const host = v.slice(0, idx)
|
||||
const port = v.slice(idx + 1)
|
||||
|
||||
// 验证端口
|
||||
const portResult = isValidPort(port)
|
||||
if (!portResult.ok) return portResult
|
||||
|
||||
// 格式:[IPv6]:port
|
||||
if (host.startsWith('[') && host.endsWith(']')) {
|
||||
const inner = host.slice(1, -1)
|
||||
return isIPv6(inner)
|
||||
}
|
||||
|
||||
// 特殊地址:0.0.0.0 (监听所有 IPv4) 或 :: (监听所有 IPv6)
|
||||
if (host === '0.0.0.0' || host === '::') {
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
// IPv4 地址
|
||||
if (validator.isIP(host, 4)) {
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
// 域名或主机名 (使用宽松的 FQDN 验证)
|
||||
if (
|
||||
validator.isFQDN(host, { require_tld: false }) ||
|
||||
validator.isAlphanumeric(host, 'en-US', { ignore: '-.' })
|
||||
) {
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
return { ok: false, error: '主机名包含非法字符' }
|
||||
}
|
||||
export const isValidListenAddressFull = (input: string | undefined): ValidationResult =>
|
||||
validateListenAddress(input, true)
|
||||
|
||||
Reference in New Issue
Block a user