From b7978705af4ba74f4e41c9c1184ca675e77f93b8 Mon Sep 17 00:00:00 2001 From: wonfen Date: Fri, 17 Jul 2026 23:51:29 +0800 Subject: [PATCH] fix: support VLESS xhttp and normalize IPv6 addresses --- Changelog.md | 1 + src/types/global.d.ts | 10 +++++++++- src/utils/uri-parser/helpers.ts | 6 +++++- src/utils/uri-parser/ss.ts | 3 ++- src/utils/uri-parser/ssr.ts | 3 ++- src/utils/uri-parser/vless.ts | 20 +++++++++++++++++++- src/utils/uri-parser/vmess.ts | 6 ++++-- 7 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index de27ab89b..ffc725fa4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -39,6 +39,7 @@ - 修复编辑订阅配置时清空 HTTP 请求超时会报错的问题 - 修复纯 IP 连接的主机地址显示错误,并保留目标 IP 为空时的地址回退 - 修复 Windows 管理员模式下启动 TUN 时因等待不可用 Service 导致延迟 30 秒的问题 +- 修复 VLESS xhttp 链接解析及 IPv6 地址格式问题
✨ 新增功能 diff --git a/src/types/global.d.ts b/src/types/global.d.ts index d70752f79..5fe0b9a84 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -353,6 +353,12 @@ interface GrpcOptions { 'grpc-service-name'?: string } +interface XHttpOptions { + path?: string + host?: string + mode?: string +} + interface RealityOptions { 'public-key'?: string 'short-id'?: string @@ -367,7 +373,7 @@ type ClientFingerprint = | '360' | 'qq' | 'random' -type NetworkType = 'ws' | 'http' | 'h2' | 'grpc' | 'tcp' +type NetworkType = 'ws' | 'http' | 'h2' | 'grpc' | 'tcp' | 'xhttp' type CipherType = | 'none' | 'auto' @@ -599,6 +605,7 @@ interface IProxyVlessConfig extends IProxyBaseConfig { 'h2-opts'?: H2Options 'grpc-opts'?: GrpcOptions 'ws-opts'?: WsOptions + 'xhttp-opts'?: XHttpOptions 'ws-path'?: string 'ws-headers'?: { [key: string]: string @@ -608,6 +615,7 @@ interface IProxyVlessConfig extends IProxyBaseConfig { servername?: string 'client-fingerprint'?: ClientFingerprint smux?: boolean + encryption?: string } // vmess interface IProxyVmessConfig extends IProxyBaseConfig { diff --git a/src/utils/uri-parser/helpers.ts b/src/utils/uri-parser/helpers.ts index 81ad15db8..87adc64b8 100644 --- a/src/utils/uri-parser/helpers.ts +++ b/src/utils/uri-parser/helpers.ts @@ -193,6 +193,10 @@ export type UrlLikeParts = { fragment?: string } +export function normalizeHost(host: string): string { + return host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host +} + const URLLIKE_RE = /^(?:(?.*?)@)?(?.*?)(?::(?\d+))?\/?(?:\?(?.*?))?(?:#(?.*?))?$/ @@ -227,7 +231,7 @@ export function parseUrlLike( const result: UrlLikeParts = { auth, - host: groups.host, + host: normalizeHost(groups.host), port: groups.port, query: groups.query, fragment: groups.fragment, diff --git a/src/utils/uri-parser/ss.ts b/src/utils/uri-parser/ss.ts index cda3c2c57..7c59098a1 100644 --- a/src/utils/uri-parser/ss.ts +++ b/src/utils/uri-parser/ss.ts @@ -4,6 +4,7 @@ import { getCipher, getIfNotBlank, getIfPresent, + normalizeHost, parseBoolOrPresence, parseQueryString, parseRequiredPort, @@ -37,7 +38,7 @@ export function URI_SS(line: string): IProxyShadowsocksConfig { if (portIdx === -1) { throw new Error('Invalid ss uri: missing port') } - const server = serverAndPort.slice(0, portIdx) + const server = normalizeHost(serverAndPort.slice(0, portIdx)) const portRaw = serverAndPort.slice(portIdx + 1) const port = parseRequiredPort(portRaw, 'Invalid ss uri: invalid port') diff --git a/src/utils/uri-parser/ssr.ts b/src/utils/uri-parser/ssr.ts index 0030caf36..15a10d18c 100644 --- a/src/utils/uri-parser/ssr.ts +++ b/src/utils/uri-parser/ssr.ts @@ -2,6 +2,7 @@ import { decodeBase64OrOriginal, getCipher, getIfNotBlank, + normalizeHost, parseQueryString, parseRequiredPort, stripUriScheme, @@ -27,7 +28,7 @@ export function URI_SSR(uri: string): IProxyshadowsocksRConfig { if (portIdx === -1) { throw new Error('Invalid ssr uri: missing port') } - const server = serverAndPort.substring(0, portIdx) + const server = normalizeHost(serverAndPort.substring(0, portIdx)) const port = parseRequiredPort( serverAndPort.substring(portIdx + 1), 'Invalid ssr uri: invalid port', diff --git a/src/utils/uri-parser/vless.ts b/src/utils/uri-parser/vless.ts index 022481101..249bb54c8 100644 --- a/src/utils/uri-parser/vless.ts +++ b/src/utils/uri-parser/vless.ts @@ -91,6 +91,8 @@ export function URI_VLESS(line: string): IProxyVlessConfig { uuid, } + proxy.encryption = params.encryption === 'none' ? '' : params.encryption + proxy.tls = (params.security && params.security !== 'none') || undefined if (isShadowrocket && parseBool(params.tls) === true) { proxy.tls = true @@ -131,7 +133,10 @@ export function URI_VLESS(line: string): IProxyVlessConfig { if (type === 'httpupgrade') { network = 'ws' httpupgrade = true - } else if (type && ['tcp', 'ws', 'http', 'grpc', 'h2'].includes(type)) { + } else if ( + type && + ['tcp', 'ws', 'http', 'grpc', 'h2', 'xhttp'].includes(type) + ) { network = type as NetworkType } else { network = 'tcp' @@ -206,6 +211,19 @@ export function URI_VLESS(line: string): IProxyVlessConfig { } break } + case 'xhttp': { + const xhttpOpts: XHttpOptions = {} + const hostVal = getIfNotBlank(host) + const pathVal = getIfNotBlank(path) + const modeVal = getIfNotBlank(params.mode) + if (hostVal) xhttpOpts.host = hostVal + if (pathVal) xhttpOpts.path = pathVal + if (modeVal) xhttpOpts.mode = modeVal + if (Object.keys(xhttpOpts).length > 0) { + proxy['xhttp-opts'] = xhttpOpts + } + break + } default: break } diff --git a/src/utils/uri-parser/vmess.ts b/src/utils/uri-parser/vmess.ts index 6bf117c25..bca0a74f8 100644 --- a/src/utils/uri-parser/vmess.ts +++ b/src/utils/uri-parser/vmess.ts @@ -5,6 +5,7 @@ import { getIfNotBlank, getIfPresent, isPresent, + normalizeHost, parseBool, parseRequiredPort, safeDecodeURIComponent, @@ -72,7 +73,7 @@ function parseVmessQuantumult(content: string): IProxyVmessConfig { const proxy: IProxyVmessConfig = { name: partitions[0].split('=')[0].trim(), type: 'vmess', - server: partitions[1], + server: normalizeHost(partitions[1]), port: parseRequiredPort(partitions[2], 'Invalid vmess uri: invalid port'), cipher: getCipher(getIfNotBlank(partitions[3], 'auto')), uuid: partitions[4].match(/^"(.*)"$/)?.[1] || '', @@ -118,7 +119,8 @@ export function URI_VMESS(line: string): IProxyVmessConfig { } const params = parseVmessParams(content, raw) - const server = params.add + const server = + typeof params.add === 'string' ? normalizeHost(params.add) : params.add const port = parseRequiredPort(params.port, 'Invalid vmess uri: invalid port') const tlsValue = params.tls const proxy: IProxyVmessConfig = {