mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 18:24:10 +00:00
fix: parse XHTTP extra fields from V2Ray links and v2rayN JSON imports (#4426)
- fromVmessLink: parse all XHTTP bidirectional fields (xPaddingBytes, xPaddingObfsMode, session/seq/uplink placements & keys, scMaxEachPostBytes, headers) from VMess share link JSON - fromParamLink: parse same missing fields from the extra JSON param in VLESS/Trojan/SS share links and from URL params - VLESSSettings.fromJson: handle v2rayN-style nested vnext array for address/port/id/flow/encryption; previously only flat format was accepted - StreamSettings.fromJson: accept splithttpSettings as backward-compat alias for xhttpSettings, normalize splithttp network to xhttp Closes #4406 Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
parent
758e1ad050
commit
fd3770c8c9
1 changed files with 58 additions and 6 deletions
|
|
@ -1138,8 +1138,12 @@ export class StreamSettings extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
// Xray-core supports both "xhttpSettings" and "splithttpSettings" (backward-compat alias)
|
||||||
|
const xhttpJson = json.xhttpSettings ?? json.splithttpSettings;
|
||||||
|
// Normalize "splithttp" network name to "xhttp" for internal consistency
|
||||||
|
const network = json.network === 'splithttp' ? 'xhttp' : json.network;
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
network,
|
||||||
json.security,
|
json.security,
|
||||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
RealityStreamSettings.fromJson(json.realitySettings),
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
|
|
@ -1148,7 +1152,7 @@ export class StreamSettings extends CommonClass {
|
||||||
WsStreamSettings.fromJson(json.wsSettings),
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
xHTTPStreamSettings.fromJson(xhttpJson),
|
||||||
HysteriaStreamSettings.fromJson(json.hysteriaSettings),
|
HysteriaStreamSettings.fromJson(json.hysteriaSettings),
|
||||||
FinalMaskStreamSettings.fromJson(json.finalmask),
|
FinalMaskStreamSettings.fromJson(json.finalmask),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
|
|
@ -1379,12 +1383,28 @@ export class Outbound extends CommonClass {
|
||||||
} else if (network === 'httpupgrade') {
|
} else if (network === 'httpupgrade') {
|
||||||
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
|
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
|
||||||
} else if (network === 'xhttp') {
|
} else if (network === 'xhttp') {
|
||||||
// xHTTPStreamSettings positional args are (path, host, headers, ..., mode);
|
|
||||||
// passing `json.mode` as the 3rd argument used to land in the `headers`
|
|
||||||
// slot, dropping the mode on the floor. Build the object and set mode
|
|
||||||
// explicitly to avoid that.
|
|
||||||
const xh = new xHTTPStreamSettings(json.path, json.host);
|
const xh = new xHTTPStreamSettings(json.path, json.host);
|
||||||
if (json.mode) xh.mode = json.mode;
|
if (json.mode) xh.mode = json.mode;
|
||||||
|
if (json.type && !json.mode) xh.mode = json.type;
|
||||||
|
// Padding / obfuscation — sing-box families use x_padding_bytes,
|
||||||
|
// while the extra block carries xPaddingBytes.
|
||||||
|
if (json.x_padding_bytes && !json.xPaddingBytes) json.xPaddingBytes = json.x_padding_bytes;
|
||||||
|
if (typeof json.xPaddingBytes === 'string' && json.xPaddingBytes) xh.xPaddingBytes = json.xPaddingBytes;
|
||||||
|
if (json.xPaddingObfsMode === true) {
|
||||||
|
xh.xPaddingObfsMode = true;
|
||||||
|
["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => {
|
||||||
|
if (typeof json[k] === 'string' && json[k]) xh[k] = json[k];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Bidirectional string fields carried in the extra block
|
||||||
|
const xFields = ["sessionPlacement", "sessionKey", "seqPlacement", "seqKey", "uplinkDataPlacement", "uplinkDataKey", "scMaxEachPostBytes"];
|
||||||
|
xFields.forEach(k => {
|
||||||
|
if (typeof json[k] === 'string' && json[k]) xh[k] = json[k];
|
||||||
|
});
|
||||||
|
// Headers — VMess extra emits them as a {name: value} map
|
||||||
|
if (json.headers && typeof json.headers === 'object' && !Array.isArray(json.headers)) {
|
||||||
|
xh.headers = Object.entries(json.headers).map(([name, value]) => ({ name, value }));
|
||||||
|
}
|
||||||
stream.xhttp = xh;
|
stream.xhttp = xh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1455,6 +1475,16 @@ export class Outbound extends CommonClass {
|
||||||
["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => {
|
["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => {
|
||||||
if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k];
|
if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k];
|
||||||
});
|
});
|
||||||
|
if (!xh.mode && typeof extra.mode === 'string' && extra.mode) xh.mode = extra.mode;
|
||||||
|
// Bidirectional string fields carried inside the extra block
|
||||||
|
const xFields = ["sessionPlacement", "sessionKey", "seqPlacement", "seqKey", "uplinkDataPlacement", "uplinkDataKey", "scMaxEachPostBytes"];
|
||||||
|
xFields.forEach(k => {
|
||||||
|
if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k];
|
||||||
|
});
|
||||||
|
// Headers — extra emits them as a {name: value} map
|
||||||
|
if (extra.headers && typeof extra.headers === 'object' && !Array.isArray(extra.headers)) {
|
||||||
|
xh.headers = Object.entries(extra.headers).map(([name, value]) => ({ name, value }));
|
||||||
|
}
|
||||||
} catch (_) { /* ignore malformed extra */ }
|
} catch (_) { /* ignore malformed extra */ }
|
||||||
}
|
}
|
||||||
stream.xhttp = xh;
|
stream.xhttp = xh;
|
||||||
|
|
@ -1997,6 +2027,28 @@ Outbound.VLESSSettings = class extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
// Handle v2rayN-style nested vnext array (standard Xray JSON format)
|
||||||
|
if (!ObjectUtil.isArrEmpty(json.vnext)) {
|
||||||
|
const v = json.vnext[0] || {};
|
||||||
|
const u = ObjectUtil.isArrEmpty(v.users) ? {} : v.users[0];
|
||||||
|
const saved = json.testseed;
|
||||||
|
const testseed = (Array.isArray(saved)
|
||||||
|
&& saved.length === 4
|
||||||
|
&& saved.every(v => Number.isInteger(v) && v > 0))
|
||||||
|
? saved
|
||||||
|
: [];
|
||||||
|
return new Outbound.VLESSSettings(
|
||||||
|
v.address,
|
||||||
|
v.port,
|
||||||
|
u.id,
|
||||||
|
u.flow,
|
||||||
|
u.encryption,
|
||||||
|
json.reverse?.tag || '',
|
||||||
|
ReverseSniffing.fromJson(json.reverse?.sniffing || {}),
|
||||||
|
json.testpre || 0,
|
||||||
|
testseed,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (ObjectUtil.isEmpty(json.address) || ObjectUtil.isEmpty(json.port)) return new Outbound.VLESSSettings();
|
if (ObjectUtil.isEmpty(json.address) || ObjectUtil.isEmpty(json.port)) return new Outbound.VLESSSettings();
|
||||||
const saved = json.testseed;
|
const saved = json.testseed;
|
||||||
const testseed = (Array.isArray(saved)
|
const testseed = (Array.isArray(saved)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue