fix: support VLESS xhttp and normalize IPv6 addresses

This commit is contained in:
wonfen
2026-07-17 23:51:29 +08:00
parent 950e8195fb
commit b7978705af
7 changed files with 42 additions and 7 deletions
+1
View File
@@ -39,6 +39,7 @@
- 修复编辑订阅配置时清空 HTTP 请求超时会报错的问题
- 修复纯 IP 连接的主机地址显示错误,并保留目标 IP 为空时的地址回退
- 修复 Windows 管理员模式下启动 TUN 时因等待不可用 Service 导致延迟 30 秒的问题
- 修复 VLESS xhttp 链接解析及 IPv6 地址格式问题
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
+9 -1
View File
@@ -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 {
+5 -1
View File
@@ -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 =
/^(?:(?<auth>.*?)@)?(?<host>.*?)(?::(?<port>\d+))?\/?(?:\?(?<query>.*?))?(?:#(?<fragment>.*?))?$/
@@ -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,
+2 -1
View File
@@ -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')
+2 -1
View File
@@ -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',
+19 -1
View File
@@ -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
}
+4 -2
View File
@@ -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 = {