refactor: streamline validation utilities

This commit is contained in:
Memory
2026-08-28 21:51:20 +08:00
committed by GitHub
parent 061faeefd1
commit 26dd08e07b

View File

@@ -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)