Skip to content

Commit 4c3da54

Browse files
committed
fix: 修复并统一 VLESS XHTTP 的 host/mode 映射与继承
解析器与生成器同步处理 xhttp-opts 的 mode,避免空值字段被误判为非法 把 Host 头与 URI 的 host 参数分离,避免回写混淆 download-settings 支持从外层 xhttp-opts 继承 path/host/mode 同步更新 parser/producer 相关测试,覆盖下载设置与回写场景
1 parent 201e0ee commit 4c3da54

5 files changed

Lines changed: 160 additions & 109 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.22.9",
3+
"version": "2.22.10",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"scripts": {

backend/src/core/proxy-utils/parsers/index.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,9 +1036,20 @@ function URI_VLESS() {
10361036
);
10371037
}
10381038

1039+
if (
1040+
Object.prototype.hasOwnProperty.call(xhttpSettings, 'mode') &&
1041+
!isNotBlank(xhttpSettings.mode)
1042+
) {
1043+
setUnsupportedXhttpField(
1044+
unsupportedXhttpSettings,
1045+
'mode',
1046+
xhttpSettings.mode,
1047+
);
1048+
}
1049+
10391050
const inlineExtra = {};
10401051
for (const [key, value] of Object.entries(xhttpSettings)) {
1041-
if (['path', 'host', 'extra'].includes(key)) {
1052+
if (['path', 'host', 'mode', 'extra'].includes(key)) {
10421053
continue;
10431054
}
10441055
inlineExtra[key] = value;
@@ -1098,7 +1109,9 @@ function URI_VLESS() {
10981109
break;
10991110
case 'security': {
11001111
const normalizedSecurity =
1101-
typeof value === 'string' ? value.toLowerCase() : '';
1112+
typeof value === 'string'
1113+
? value.toLowerCase()
1114+
: '';
11021115
if (!['tls', 'reality'].includes(normalizedSecurity)) {
11031116
setUnsupportedXhttpField(
11041117
unsupportedDownloadSettings,
@@ -1119,7 +1132,9 @@ function URI_VLESS() {
11191132
}
11201133

11211134
const unsupportedTlsSettings = {};
1122-
for (const [tlsKey, tlsValue] of Object.entries(value)) {
1135+
for (const [tlsKey, tlsValue] of Object.entries(
1136+
value,
1137+
)) {
11231138
switch (tlsKey) {
11241139
case 'serverName':
11251140
case 'fingerprint':
@@ -1238,7 +1253,9 @@ function URI_VLESS() {
12381253
}
12391254
case 'network': {
12401255
const normalizedNetwork =
1241-
typeof value === 'string' ? value.toLowerCase() : '';
1256+
typeof value === 'string'
1257+
? value.toLowerCase()
1258+
: '';
12421259
if (
12431260
normalizedNetwork !== 'xhttp' &&
12441261
normalizedNetwork !== 'splithttp'
@@ -1501,6 +1518,10 @@ function URI_VLESS() {
15011518
parsedDownloadSettings.host =
15021519
downloadSettings.xhttpSettings.host;
15031520
}
1521+
if (isNotBlank(downloadSettings.xhttpSettings.mode)) {
1522+
parsedDownloadSettings.mode =
1523+
downloadSettings.xhttpSettings.mode;
1524+
}
15041525
applyXhttpExtraFields(
15051526
parsedDownloadSettings,
15061527
downloadSettings.xhttpSettings,
@@ -1651,6 +1672,13 @@ function URI_VLESS() {
16511672
} else {
16521673
opts.headers = { Host: host };
16531674
}
1675+
if (['xhttp'].includes(proxy.network) && opts.headers?.Host) {
1676+
opts.host = opts.headers.Host;
1677+
delete opts.headers.Host;
1678+
if (Object.keys(opts.headers).length === 0) {
1679+
delete opts.headers;
1680+
}
1681+
}
16541682
}
16551683
if (params.serviceName) {
16561684
opts[`${proxy.network}-service-name`] = params.serviceName;
@@ -1753,9 +1781,12 @@ function URI_VLESS() {
17531781
// Supported fields must round-trip through the structured node
17541782
// so later edits are reflected on export, while unsupported
17551783
// fields still survive VLESS URI -> node -> VLESS URI flows.
1756-
const unsupportedExtra = collectUnsupportedRootXhttpExtra(extra, {
1757-
parsedDownloadSettings: downloadSettings,
1758-
});
1784+
const unsupportedExtra = collectUnsupportedRootXhttpExtra(
1785+
extra,
1786+
{
1787+
parsedDownloadSettings: downloadSettings,
1788+
},
1789+
);
17591790
if (unsupportedExtra) {
17601791
proxy._extra_unsupported = unsupportedExtra;
17611792
}

backend/src/core/proxy-utils/producers/uri.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ function applyStructuredXhttpExtraFields(
191191
}
192192
}
193193

194-
function buildXhttpDownloadSettings(downloadSettings) {
194+
function buildXhttpDownloadSettings(downloadSettings, outerXhttpOpts = {}) {
195195
if (!isPlainObject(downloadSettings)) {
196196
return undefined;
197197
}
@@ -271,13 +271,23 @@ function buildXhttpDownloadSettings(downloadSettings) {
271271
}
272272

273273
const xhttpSettings = {};
274-
if (downloadSettings.path) {
275-
xhttpSettings.path = downloadSettings.path;
276-
}
277-
const downloadHost = getTransportHost('xhttp', downloadSettings);
274+
// Mirror Mihomo's inheritance: path and host fall back to outer xhttp-opts
275+
// when not explicitly set in download-settings. Mode is never a field in
276+
// Mihomo's XHTTPDownloadSettings struct, so it always comes from outer.
277+
const dsPath = downloadSettings.path ?? outerXhttpOpts.path;
278+
if (dsPath) {
279+
xhttpSettings.path = dsPath;
280+
}
281+
const downloadHost =
282+
getTransportHost('xhttp', downloadSettings) ??
283+
getTransportHost('xhttp', outerXhttpOpts);
278284
if (downloadHost) {
279285
xhttpSettings.host = downloadHost;
280286
}
287+
const mode = downloadSettings.mode ?? outerXhttpOpts.mode;
288+
if (mode) {
289+
xhttpSettings.mode = mode;
290+
}
281291
applyStructuredXhttpExtraFields(xhttpSettings, downloadSettings, {
282292
excludeHostHeader: true,
283293
xmuxTarget: 'extra',
@@ -298,11 +308,15 @@ function buildXhttpDownloadSettings(downloadSettings) {
298308
network: normalizedNetwork || 'xhttp',
299309
...(result.port != null ? { port: result.port } : {}),
300310
...(result.security != null ? { security: result.security } : {}),
301-
...(result.tlsSettings != null ? { tlsSettings: result.tlsSettings } : {}),
311+
...(result.tlsSettings != null
312+
? { tlsSettings: result.tlsSettings }
313+
: {}),
302314
...(result.realitySettings != null
303315
? { realitySettings: result.realitySettings }
304316
: {}),
305-
...(result.xhttpSettings != null ? { xhttpSettings: result.xhttpSettings } : {}),
317+
...(result.xhttpSettings != null
318+
? { xhttpSettings: result.xhttpSettings }
319+
: {}),
306320
};
307321
}
308322

@@ -316,6 +330,7 @@ function buildStructuredVlessExtraObject(proxy) {
316330

317331
const downloadSettings = buildXhttpDownloadSettings(
318332
xhttpOpts['download-settings'],
333+
xhttpOpts,
319334
);
320335
if (downloadSettings) {
321336
extra.downloadSettings = downloadSettings;

0 commit comments

Comments
 (0)