mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/mihomo-party-org/clash-party.git
synced 2026-09-20 08:03:39 +08:00
fix: validate system proxy bypass formats
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
- 修复 macOS 下离线状态或慢 helper 可能导致系统代理和 DNS 清理长时间阻塞退出,以及延迟 DNS 任务可能在退出恢复后再次改写 DNS 的问题
|
||||
- 修复 Windows 注销或关机时 `session-end` 与退出事件重复执行清理,并可能绕过有界退出流程的问题
|
||||
- 修复 Windows 管理员权限检测失败结果被错误缓存、跳过备用检测的问题
|
||||
- 修复系统代理绕过项未按平台格式校验,以及 Windows 绕过列表分隔符错误的问题
|
||||
|
||||
# 2.0.0
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@ async function enableSysProxy(helperTimeout?: number): Promise<void> {
|
||||
const { mode, host, bypass = defaultBypass } = sysProxy
|
||||
const { 'mixed-port': port = DEFAULT_MIHOMO_PORTS.mixed } = await getControledMihomoConfig()
|
||||
const proxyHost = host || '127.0.0.1'
|
||||
const formattedBypass = bypass
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
.join(process.platform === 'win32' ? ';' : ',')
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
// macOS 需要 helper 提权
|
||||
@@ -108,7 +112,7 @@ async function enableSysProxy(helperTimeout?: number): Promise<void> {
|
||||
await helperRequest(() =>
|
||||
axios.post(
|
||||
'http://localhost/global',
|
||||
{ host: proxyHost, port: port.toString(), bypass: bypass.join(',') },
|
||||
{ host: proxyHost, port: port.toString(), bypass: formattedBypass },
|
||||
helperAxiosOptions(helperTimeout)
|
||||
)
|
||||
)
|
||||
@@ -119,7 +123,7 @@ async function enableSysProxy(helperTimeout?: number): Promise<void> {
|
||||
if (mode === 'auto') {
|
||||
triggerAutoProxy(true, `http://${proxyHost}:${pacPort}/pac`)
|
||||
} else {
|
||||
triggerManualProxy(true, proxyHost, port, bypass.join(','))
|
||||
triggerManualProxy(true, proxyHost, port, formattedBypass)
|
||||
}
|
||||
} catch (error) {
|
||||
await proxyLogger.error('Failed to enable system proxy', error)
|
||||
|
||||
@@ -473,7 +473,10 @@
|
||||
"sysproxy.pacEditor.title": "Edit PAC Script",
|
||||
"sysproxy.bypass.title": "Proxy Bypass",
|
||||
"sysproxy.bypass.addDefault": "Add Default Bypass",
|
||||
"sysproxy.bypass.placeholder": "Example: *.baidu.com",
|
||||
"sysproxy.bypass.placeholder": "Example: {{example}}",
|
||||
"sysproxy.bypass.invalid.windows": "Enter a valid host, IP address, or Windows wildcard, for example 127.*",
|
||||
"sysproxy.bypass.invalid.unix": "Enter a valid host, IP address, CIDR network, or wildcard, for example 192.168.0.0/16",
|
||||
"sysproxy.bypass.notSaved": "Invalid bypass entries won't be saved; fix or remove them first.",
|
||||
"tun.title": "TUN",
|
||||
"tun.firewall.title": "Reset Firewall",
|
||||
"tun.firewall.reset": "Reset Firewall",
|
||||
|
||||
@@ -462,7 +462,10 @@
|
||||
"sysproxy.pacEditor.title": "ویرایش اسکریپت PAC",
|
||||
"sysproxy.bypass.title": "دور زدن پراکسی",
|
||||
"sysproxy.bypass.addDefault": "افزودن دور زدن پیشفرض",
|
||||
"sysproxy.bypass.placeholder": "مثال: *.baidu.com",
|
||||
"sysproxy.bypass.placeholder": "مثال: {{example}}",
|
||||
"sysproxy.bypass.invalid.windows": "یک میزبان، نشانی IP یا الگوی معتبر Windows وارد کنید، مانند 127.*",
|
||||
"sysproxy.bypass.invalid.unix": "یک میزبان، نشانی IP، شبکه CIDR یا الگوی معتبر وارد کنید، مانند 192.168.0.0/16",
|
||||
"sysproxy.bypass.notSaved": "موارد نامعتبر دور زدن ذخیره نمیشوند؛ ابتدا آنها را اصلاح یا حذف کنید.",
|
||||
"tun.title": "TUN",
|
||||
"tun.firewall.title": "بازنشانی دیوار آتش",
|
||||
"tun.firewall.reset": "بازنشانی دیوار آتش",
|
||||
|
||||
@@ -464,7 +464,10 @@
|
||||
"sysproxy.pacEditor.title": "Редактировать PAC скрипт",
|
||||
"sysproxy.bypass.title": "Исключения прокси",
|
||||
"sysproxy.bypass.addDefault": "Добавить стандартные исключения",
|
||||
"sysproxy.bypass.placeholder": "Пример: vk.com",
|
||||
"sysproxy.bypass.placeholder": "Пример: {{example}}",
|
||||
"sysproxy.bypass.invalid.windows": "Введите корректное имя хоста, IP-адрес или шаблон Windows, например 127.*",
|
||||
"sysproxy.bypass.invalid.unix": "Введите корректное имя хоста, IP-адрес, CIDR-сеть или шаблон, например 192.168.0.0/16",
|
||||
"sysproxy.bypass.notSaved": "Некорректные исключения не будут сохранены; исправьте или удалите их.",
|
||||
"tun.title": "TUN",
|
||||
"tun.firewall.title": "Сбросить брандмауэр",
|
||||
"tun.firewall.reset": "Сбросить брандмауэр",
|
||||
|
||||
@@ -473,7 +473,10 @@
|
||||
"sysproxy.pacEditor.title": "编辑 PAC 脚本",
|
||||
"sysproxy.bypass.title": "代理绕过",
|
||||
"sysproxy.bypass.addDefault": "添加默认代理绕过",
|
||||
"sysproxy.bypass.placeholder": "例:*.baidu.com",
|
||||
"sysproxy.bypass.placeholder": "例:{{example}}",
|
||||
"sysproxy.bypass.invalid.windows": "请输入有效的主机、IP 地址或 Windows 通配符,例如 127.*",
|
||||
"sysproxy.bypass.invalid.unix": "请输入有效的主机、IP 地址、CIDR 网段或通配符,例如 192.168.0.0/16",
|
||||
"sysproxy.bypass.notSaved": "无效的代理绕过项不会被保存,请先修正或删除。",
|
||||
"tun.title": "虚拟网卡",
|
||||
"tun.firewall.title": "重设防火墙",
|
||||
"tun.firewall.reset": "重设防火墙",
|
||||
|
||||
@@ -473,7 +473,10 @@
|
||||
"sysproxy.pacEditor.title": "編輯 PAC 腳本",
|
||||
"sysproxy.bypass.title": "代理繞過",
|
||||
"sysproxy.bypass.addDefault": "添加默認代理繞過",
|
||||
"sysproxy.bypass.placeholder": "例:*.baidu.com",
|
||||
"sysproxy.bypass.placeholder": "例:{{example}}",
|
||||
"sysproxy.bypass.invalid.windows": "請輸入有效的主機、IP 位址或 Windows 萬用字元,例如 127.*",
|
||||
"sysproxy.bypass.invalid.unix": "請輸入有效的主機、IP 位址、CIDR 網段或萬用字元,例如 192.168.0.0/16",
|
||||
"sysproxy.bypass.notSaved": "無效的代理繞過項不會被儲存,請先修正或刪除。",
|
||||
"tun.title": "虛擬網卡",
|
||||
"tun.firewall.title": "重設防火牆",
|
||||
"tun.firewall.reset": "重設防火牆",
|
||||
|
||||
@@ -7,6 +7,7 @@ import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { openUWPTool, triggerSysProxy } from '@renderer/utils/ipc'
|
||||
import { sysProxyBypassValidator } from '@renderer/utils/validate'
|
||||
import React, { Key, useState } from 'react'
|
||||
import { MdDeleteForever } from 'react-icons/md'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -73,6 +74,20 @@ const Sysproxy: React.FC = () => {
|
||||
setChanged(true)
|
||||
}
|
||||
|
||||
const normalizedBypass = values.bypass.map((entry) => entry.trim()).filter(Boolean)
|
||||
const hasInvalidBypass = normalizedBypass.some(
|
||||
(entry) => !sysProxyBypassValidator(entry, platform)
|
||||
)
|
||||
const bypassInputs = hasInvalidBypass ? values.bypass : [...values.bypass, '']
|
||||
const bypassExample = platform === 'win32' ? '127.*' : '192.168.0.0/16'
|
||||
const getBypassError = (entry: string): string | undefined => {
|
||||
const trimmedEntry = entry.trim()
|
||||
if (trimmedEntry === '' || sysProxyBypassValidator(trimmedEntry, platform)) return undefined
|
||||
return t(
|
||||
platform === 'win32' ? 'sysproxy.bypass.invalid.windows' : 'sysproxy.bypass.invalid.unix'
|
||||
)
|
||||
}
|
||||
|
||||
const [openPacEditor, setOpenPacEditor] = useState(false)
|
||||
|
||||
const handleBypassChange = (value: string, index: number): void => {
|
||||
@@ -96,9 +111,16 @@ const Sysproxy: React.FC = () => {
|
||||
|
||||
// 保存当前的开关状态,以便在失败时恢复
|
||||
const previousState = values.enable
|
||||
const sysProxyPatch: ISysProxyConfig = { ...values }
|
||||
if (hasInvalidBypass) {
|
||||
// 存在非法条目时不覆盖已保存的绕过列表,其它系统代理设置照常保存
|
||||
delete sysProxyPatch.bypass
|
||||
} else {
|
||||
sysProxyPatch.bypass = normalizedBypass
|
||||
}
|
||||
|
||||
try {
|
||||
await patchAppConfig({ sysProxy: values })
|
||||
await patchAppConfig({ sysProxy: sysProxyPatch })
|
||||
await triggerSysProxy(true)
|
||||
|
||||
await patchAppConfig({ sysProxy: { enable: true } })
|
||||
@@ -189,13 +211,15 @@ const Sysproxy: React.FC = () => {
|
||||
</SettingItem>
|
||||
<div className="flex flex-col items-stretch">
|
||||
<h3 className="mb-2">{t('sysproxy.bypass.title')}</h3>
|
||||
{[...values.bypass, ''].map((domain, index) => (
|
||||
{bypassInputs.map((domain, index) => (
|
||||
<div key={index} className="mb-2 flex">
|
||||
<Input
|
||||
fullWidth
|
||||
size="sm"
|
||||
placeholder={t('sysproxy.bypass.placeholder')}
|
||||
placeholder={t('sysproxy.bypass.placeholder', { example: bypassExample })}
|
||||
value={domain}
|
||||
isInvalid={Boolean(getBypassError(domain))}
|
||||
errorMessage={getBypassError(domain)}
|
||||
onValueChange={(v) => handleBypassChange(v, index)}
|
||||
/>
|
||||
{index < values.bypass.length && (
|
||||
@@ -211,6 +235,9 @@ const Sysproxy: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{hasInvalidBypass && (
|
||||
<div className="px-1 text-xs text-danger">{t('sysproxy.bypass.notSaved')}</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -40,7 +40,7 @@ const portValidator = (value: string): boolean => {
|
||||
}
|
||||
|
||||
const ipv4CIDRValidator = (value: string): boolean => {
|
||||
// 验证 IPv4 CIDR 格式 (例如: 192.168.1.0/24)
|
||||
// 验证 IPv4 CIDR 格式 (例如:192.168.1.0/24)
|
||||
if (!value.includes('/')) return false
|
||||
|
||||
const [ip, cidr] = value.split('/')
|
||||
@@ -50,7 +50,7 @@ const ipv4CIDRValidator = (value: string): boolean => {
|
||||
}
|
||||
|
||||
const ipv6CIDRValidator = (value: string): boolean => {
|
||||
// 验证 IPv6 CIDR 格式 (例如: 2001:db8::/32)
|
||||
// 验证 IPv6 CIDR 格式 (例如:2001:db8::/32)
|
||||
if (!value.includes('/')) return false
|
||||
|
||||
const [ip, cidr] = value.split('/')
|
||||
@@ -70,6 +70,39 @@ const ipCIDRValidator = (value: string): boolean => {
|
||||
return ipv4CIDRValidator(value) || ipv6CIDRValidator(value)
|
||||
}
|
||||
|
||||
const sysProxyBypassValidator = (
|
||||
value: string,
|
||||
targetPlatform: NodeJS.Platform | string
|
||||
): boolean => {
|
||||
const entry = value.trim()
|
||||
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
|
||||
}
|
||||
|
||||
if (targetPlatform === 'win32' && /[*?]/.test(entry)) {
|
||||
const normalizedPattern = entry.replace(/\*/g, 'wildcard').replace(/\?/g, 'q')
|
||||
return validator.isFQDN(normalizedPattern, {
|
||||
require_tld: false,
|
||||
allow_numeric_tld: true
|
||||
})
|
||||
}
|
||||
|
||||
return validator.isFQDN(entry, {
|
||||
require_tld: false,
|
||||
allow_numeric_tld: true,
|
||||
allow_wildcard: true
|
||||
})
|
||||
}
|
||||
|
||||
// DOMAIN-WILDCARD 验证器 - 仅支持 * 和 ? 通配符
|
||||
const domainWildcardValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
@@ -90,25 +123,25 @@ const geositeValidator = (value: string): boolean => {
|
||||
|
||||
// GEOIP 验证器 - 国家代码验证(ISO 3166-1 alpha-2)
|
||||
const geoipValidator = (value: string): boolean => {
|
||||
// 支持2位国家代码(大小写不敏感)
|
||||
// 支持 2 位国家代码(大小写不敏感)
|
||||
return validator.isAlpha(value) && value.length === 2
|
||||
}
|
||||
|
||||
// ASN 验证器 - 自治系统号验证
|
||||
const asnValidator = (value: string): boolean => {
|
||||
// ASN 范围: 1 - 4294967295 (32-bit)
|
||||
// ASN 范围:1 - 4294967295 (32-bit)
|
||||
return validator.isInt(value, { min: 1, max: 4294967295 })
|
||||
}
|
||||
|
||||
// UID 验证器 - Linux 用户 ID 验证
|
||||
const uidValidator = (value: string): boolean => {
|
||||
// UID 范围: 0 - 65535 (大多数系统)
|
||||
// UID 范围:0 - 65535 (大多数系统)
|
||||
return validator.isInt(value, { min: 0, max: 65535 })
|
||||
}
|
||||
|
||||
// DSCP 验证器 - 区分服务代码点验证
|
||||
const dscpValidator = (value: string): boolean => {
|
||||
// DSCP 范围: 0 - 63 (6-bit)
|
||||
// DSCP 范围:0 - 63 (6-bit)
|
||||
return validator.isInt(value, { min: 0, max: 63 })
|
||||
}
|
||||
|
||||
@@ -218,7 +251,7 @@ const logicRuleValidator = (value: string): boolean => {
|
||||
// SUB-RULE 验证器 - 子规则验证
|
||||
const subRuleValidator = (value: string): boolean => {
|
||||
if (value.length === 0) return false
|
||||
// 格式: (RULE_TYPE,payload) 或 provider_name
|
||||
// 格式:(RULE_TYPE,payload) 或 provider_name
|
||||
if (value.startsWith('(') && value.endsWith(')')) {
|
||||
return logicRuleValidator(value)
|
||||
}
|
||||
@@ -228,7 +261,7 @@ const subRuleValidator = (value: string): boolean => {
|
||||
|
||||
// 端口范围验证器(支持单个端口或范围)
|
||||
const portRangeValidator = (value: string): boolean => {
|
||||
// 支持单个端口或范围格式,如: 80 或 8000-9000
|
||||
// 支持单个端口或范围格式,如:80 或 8000-9000
|
||||
if (value.includes('-')) {
|
||||
const [start, end] = value.split('-')
|
||||
return validator.isPort(start) && validator.isPort(end) && parseInt(start) <= parseInt(end)
|
||||
@@ -264,7 +297,8 @@ export {
|
||||
portRangeValidator,
|
||||
ipv4CIDRValidator,
|
||||
ipv6CIDRValidator,
|
||||
ipCIDRValidator
|
||||
ipCIDRValidator,
|
||||
sysProxyBypassValidator
|
||||
}
|
||||
|
||||
// 通用验证结果类型
|
||||
@@ -303,7 +337,7 @@ export const isValidListenAddress = (s: string | undefined): ValidationResult =>
|
||||
|
||||
const v = s.trim()
|
||||
|
||||
// 格式: :port (仅端口)
|
||||
// 格式::port (仅端口)
|
||||
if (v.startsWith(':')) {
|
||||
return isValidPort(v.slice(1))
|
||||
}
|
||||
@@ -318,7 +352,7 @@ export const isValidListenAddress = (s: string | undefined): ValidationResult =>
|
||||
const portResult = isValidPort(port)
|
||||
if (!portResult.ok) return portResult
|
||||
|
||||
// 格式: [IPv6]:port
|
||||
// 格式:[IPv6]:port
|
||||
if (host.startsWith('[') && host.endsWith(']')) {
|
||||
const inner = host.slice(1, -1)
|
||||
return isIPv6(inner)
|
||||
@@ -346,7 +380,7 @@ export const isValidListenAddressFull = (s: string | undefined): ValidationResul
|
||||
|
||||
const v = s.trim()
|
||||
|
||||
// 格式: :port (仅端口)
|
||||
// 格式::port (仅端口)
|
||||
if (v.startsWith(':')) {
|
||||
return isValidPort(v.slice(1))
|
||||
}
|
||||
@@ -361,13 +395,13 @@ export const isValidListenAddressFull = (s: string | undefined): ValidationResul
|
||||
const portResult = isValidPort(port)
|
||||
if (!portResult.ok) return portResult
|
||||
|
||||
// 格式: [IPv6]:port
|
||||
// 格式:[IPv6]:port
|
||||
if (host.startsWith('[') && host.endsWith(']')) {
|
||||
const inner = host.slice(1, -1)
|
||||
return isIPv6(inner)
|
||||
}
|
||||
|
||||
// 特殊地址: 0.0.0.0 (监听所有 IPv4) 或 :: (监听所有 IPv6)
|
||||
// 特殊地址:0.0.0.0 (监听所有 IPv4) 或 :: (监听所有 IPv6)
|
||||
if (host === '0.0.0.0' || host === '::') {
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user