mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-14 21:20:48 +08:00
fix(proxy): validate user-entered URLs via isValidUrl
Delay test inputs now reject non-http(s) URLs with a warning toast and fall back to the default URL instead of erroring every node in the core. Subscription import and profile card host parsing reuse the same validator, fixing bare-domain URLs shown as local files and credential-bearing URLs leaking into the card.
This commit is contained in:
@@ -27,6 +27,7 @@ import { useNavigate } from 'react-router'
|
||||
import { useAppRefreshers } from '@/providers/app-data-context'
|
||||
import { updateProfile } from '@/services/cmds'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { isValidUrl } from '@/utils/network'
|
||||
import { openExternalUrl } from '@/utils/open-external-url'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
@@ -41,7 +42,7 @@ const round = keyframes`
|
||||
// 辅助函数解析URL和过期时间
|
||||
const parseUrl = (url?: string) => {
|
||||
if (!url) return '-'
|
||||
if (url.startsWith('http')) return new URL(url).host
|
||||
if (isValidUrl(url)) return new URL(url).host
|
||||
return 'local'
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import { showNotice } from '@/services/notice-service'
|
||||
import { useLoadingCache, useSetLoadingCache } from '@/services/states'
|
||||
import type { TranslationKey } from '@/types/generated/i18n-keys'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
import { isValidUrl } from '@/utils/network'
|
||||
import { openExternalUrl } from '@/utils/open-external-url'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
@@ -965,9 +966,7 @@ export const ProfileItem = memo(ProfileItemBase)
|
||||
|
||||
function parseUrl(url?: string) {
|
||||
if (!url) return ''
|
||||
const regex = /https?:\/\/(.+?)\//
|
||||
const result = url.match(regex)
|
||||
return result ? result[1] : 'local file'
|
||||
return isValidUrl(url) ? new URL(url).host : 'local file'
|
||||
}
|
||||
|
||||
function parseExpire(expire?: number) {
|
||||
|
||||
@@ -17,6 +17,8 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import delayManager from '@/services/delay'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { isValidUrl } from '@/utils/network'
|
||||
|
||||
import { BaseSearchBox, type SearchState } from '../base'
|
||||
|
||||
@@ -174,6 +176,10 @@ export const ProxyGroupTools = memo(function ProxyGroupTools(props: Props) {
|
||||
if (testUrl?.trim() && textState !== 'filter') {
|
||||
onHeadState({ textState: 'url' })
|
||||
}
|
||||
if (testUrl?.trim() && !isValidUrl(testUrl)) {
|
||||
showNotice.warning('proxies.feedback.warnings.invalidTestUrl')
|
||||
return
|
||||
}
|
||||
onCheckDelay()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -18,7 +18,9 @@ import { useTranslation } from 'react-i18next'
|
||||
import { BaseSearchBox } from '@/components/base'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import delayManager from '@/services/delay'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
import { isValidUrl } from '@/utils/network'
|
||||
|
||||
import type { ProxySortType } from './use-filter-sort'
|
||||
import type { HeadState } from './use-head-state'
|
||||
@@ -95,6 +97,10 @@ export const ProxyHead = ({
|
||||
debugLog(`[ProxyHead] 使用自定义测试URL: ${testUrl}`)
|
||||
onHeadState({ textState: 'url' })
|
||||
}
|
||||
if (testUrl?.trim() && !isValidUrl(testUrl)) {
|
||||
showNotice.warning('proxies.feedback.warnings.invalidTestUrl')
|
||||
return
|
||||
}
|
||||
onCheckDelay()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} 업데이트 성공",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} успешно обновлён",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} 更新成功",
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"warnings": {
|
||||
"invalidTestUrl": "Test URL must start with http:// or https://"
|
||||
},
|
||||
"notifications": {
|
||||
"provider": {
|
||||
"updateSuccess": "{{name}} updated successfully",
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
useThemeMode,
|
||||
} from '@/services/states'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
import { isValidUrl } from '@/utils/network'
|
||||
|
||||
// 与 src-tauri/src/main.rs 的 worker_limit 上限(8)保持一致,避免前后端更新风暴不对齐
|
||||
const PROFILE_UPDATE_WORKER_LIMIT = 8
|
||||
@@ -214,7 +215,7 @@ const ProfilePage = () => {
|
||||
|
||||
const onImport = async () => {
|
||||
if (!url) return
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
if (!isValidUrl(url)) {
|
||||
showNotice.error('profiles.page.feedback.errors.invalidUrl')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/types/proxy-view'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
import { classifyDelay, DEFAULT_DELAY_TIMEOUT } from '@/utils/delay'
|
||||
import { isValidUrl } from '@/utils/network'
|
||||
|
||||
export type DelaySnapshot = {
|
||||
of: (member: ResolvedProxyMember) => number
|
||||
@@ -152,6 +153,11 @@ class DelayManager {
|
||||
}
|
||||
|
||||
setUrl(group: string, url: string) {
|
||||
if (!isValidUrl(url)) {
|
||||
debugLog(`[DelayManager] 拒绝无效测试URL,组: ${group}, URL: ${url}`)
|
||||
this.urlMap.delete(group)
|
||||
return
|
||||
}
|
||||
debugLog(`[DelayManager] 设置测试URL,组: ${group}, URL: ${url}`)
|
||||
this.urlMap.set(group, url)
|
||||
}
|
||||
|
||||
@@ -324,6 +324,7 @@ export const translationKeys = [
|
||||
'proxies.page.messages.directMode',
|
||||
'proxies.page.title.default',
|
||||
'proxies.page.title.chainMode',
|
||||
'proxies.feedback.warnings.invalidTestUrl',
|
||||
'proxies.feedback.notifications.provider.updateSuccess',
|
||||
'proxies.feedback.notifications.provider.updateFailed',
|
||||
'proxies.feedback.notifications.provider.genericError',
|
||||
|
||||
@@ -500,6 +500,9 @@ export interface TranslationResources {
|
||||
updateSuccess: string
|
||||
}
|
||||
}
|
||||
warnings: {
|
||||
invalidTestUrl: string
|
||||
}
|
||||
}
|
||||
page: {
|
||||
actions: {
|
||||
|
||||
Reference in New Issue
Block a user