Browse Source

Merge pull request #1663 from zxlhhyccc/xhttp

luci-app-ssr-plus: Add Xray new version `XHTTP` transport (formerly `SplitHTTP` transport) support.
coolsnowwolf 9 months ago
parent
commit
89476a11fc

+ 3 - 3
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua

@@ -204,12 +204,12 @@ for key, server_type in pairs(type_table) do
 end
 end
 
 
 -- Socks User
 -- Socks User
-o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when auth is password valid, Mandatory."))
+o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when Socks5 Auth Mode is password valid, Mandatory."))
 o.rmempty = true
 o.rmempty = true
 o:depends("socks5_auth", "password")
 o:depends("socks5_auth", "password")
 
 
 -- Socks Password
 -- Socks Password
-o = s:option(Value, "socks5_pass", translate("Socks5 Password"), translate("Only when auth is password valid, Not mandatory."))
+o = s:option(Value, "socks5_pass", translate("Socks5 Password"), translate("Only when Socks5 Auth Mode is password valid, Not mandatory."))
 o.password = true
 o.password = true
 o.rmempty = true
 o.rmempty = true
 o:depends("socks5_auth", "password")
 o:depends("socks5_auth", "password")
@@ -263,7 +263,7 @@ o.default = 0
 s = m:section(TypedSection, "xray_noise_packets", translate("Xray Noise Packets"))
 s = m:section(TypedSection, "xray_noise_packets", translate("Xray Noise Packets"))
 s.description = translate(
 s.description = translate(
     "<font style='color:red'>" .. translate("To send noise packets, select \"Noise\" in Xray Settings.") .. "</font>" ..
     "<font style='color:red'>" .. translate("To send noise packets, select \"Noise\" in Xray Settings.") .. "</font>" ..
-    "<br/><font><b>" .. translate("For specific usage, see: ") .. "</b></font>" ..
+    "<br/><font><b>" .. translate("For specific usage, see:") .. "</b></font>" ..
     "<a href='https://xtls.github.io/config/outbounds/freedom.html' target='_blank'>" ..
     "<a href='https://xtls.github.io/config/outbounds/freedom.html' target='_blank'>" ..
     "<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
     "<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
 s.template = "cbi/tblsection"
 s.template = "cbi/tblsection"

+ 74 - 1
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua

@@ -4,6 +4,7 @@
 require "nixio.fs"
 require "nixio.fs"
 require "luci.sys"
 require "luci.sys"
 require "luci.http"
 require "luci.http"
+require "luci.jsonc"
 require "luci.model.ipkg"
 require "luci.model.ipkg"
 
 
 local m, s, o
 local m, s, o
@@ -504,7 +505,6 @@ o.rmempty = true
 o.default = ""
 o.default = ""
 o:depends("type", "tuic")
 o:depends("type", "tuic")
 
 
-
 o = s:option(ListValue, "udp_relay_mode", translate("UDP relay mode"))
 o = s:option(ListValue, "udp_relay_mode", translate("UDP relay mode"))
 o:depends("type", "tuic")
 o:depends("type", "tuic")
 o:value("native", translate("native UDP characteristics"))
 o:value("native", translate("native UDP characteristics"))
@@ -623,6 +623,7 @@ o:value("kcp", "mKCP")
 o:value("ws", "WebSocket")
 o:value("ws", "WebSocket")
 o:value("httpupgrade", "HTTPUpgrade")
 o:value("httpupgrade", "HTTPUpgrade")
 o:value("splithttp", "SplitHTTP")
 o:value("splithttp", "SplitHTTP")
+o:value("xhttp", "XHTTP")
 o:value("h2", "HTTP/2")
 o:value("h2", "HTTP/2")
 o:value("quic", "QUIC")
 o:value("quic", "QUIC")
 o:value("grpc", "gRPC")
 o:value("grpc", "gRPC")
@@ -703,6 +704,78 @@ o = s:option(Value, "splithttp_path", translate("Splithttp Path"))
 o:depends("transport", "splithttp")
 o:depends("transport", "splithttp")
 o.rmempty = true
 o.rmempty = true
 
 
+-- [[ XHTTP部分 ]]--
+o = s:option(ListValue, "xhttp_alpn", translate("XHTTP Alpn"))
+o.default = ""
+o:value("", translate("Default"))
+o:value("h3")
+o:value("h2")
+o:value("h3,h2")
+o:value("http/1.1")
+o:value("h2,http/1.1")
+o:value("h3,h2,http/1.1")
+o:depends("transport", "xhttp")
+
+o = s:option(ListValue, "xhttp_mode", translate("XHTTP Mode"))
+o:depends("transport", "xhttp")
+o.default = "auto"
+o:value("auto")
+o:value("packet-up")
+o:value("stream-up")
+o:value("stream-one")
+
+o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
+o:depends({transport = "xhttp", tls = false})
+o.rmempty = true
+
+o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
+o.placeholder = "/"
+o:depends("transport", "xhttp")
+o.rmempty = true
+
+o = s:option(Flag, "enable_xhttp_extra", translate("XHTTP Extra"))
+o.description = translate("Enable this option to configure XHTTP Extra (JSON format).")
+o.default = "0"
+o.rmempty = false
+o:depends("transport", "xhttp")
+
+o = s:option(TextValue, "xhttp_extra", " ")
+o.description = translate(
+    "<font><b>" .. translate("Configure XHTTP Extra Settings (JSON format), see:") .. "</b></font>" ..
+    " <a href='https://xtls.github.io/config/transports/splithttp.html#extra' target='_blank'>" ..
+    "<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
+o:depends("enable_xhttp_extra", true)
+o.rmempty = true
+o.rows = 10
+o.wrap = "off"
+o.custom_write = function(self, section, value)
+    m:set(section, "xhttp_extra", value)
+    local success, data = pcall(luci.jsonc.parse, value)
+    if success and data then
+        local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
+            or (data.downloadSettings and data.downloadSettings.address)
+        if address and address ~= "" then
+            m:set(section, "download_address", address)
+        else
+            m:del(section, "download_address")
+        end
+    else
+        m:del(section, "download_address")
+    end
+end
+o.validate = function(self, value)
+    value = value:gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
+    if value:sub(-1) == "\n" then
+        value = value:sub(1, -2)
+    end
+    local success, data = pcall(luci.jsonc.parse, value)
+    if not success or not data then
+        return nil, translate("Invalid JSON format")
+    end
+
+    return value
+end
+
 -- [[ H2部分 ]]--
 -- [[ H2部分 ]]--
 
 
 -- H2域名
 -- H2域名

+ 36 - 0
luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm

@@ -263,6 +263,18 @@ function import_ssr_url(btn, urlname, sid) {
 				}
 				}
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
 				break;
 				break;
+			case "xhttp":
+				if (params.get("security") !== "tls") {
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
+				}
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = params.get("mode") || "auto";
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
+				if (params.get("extra") && params.get("extra").trim() !== "") {
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = params.get("extra") || "";
+				}
+				break;
 			case "kcp":
 			case "kcp":
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
@@ -338,6 +350,16 @@ function import_ssr_url(btn, urlname, sid) {
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = ssm.host;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = ssm.host;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = ssm.path;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = ssm.path;
 			}
 			}
+			if (ssm.net == "xhttp") {
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = ssm.mode;
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = ssm.host;
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = ssm.path;
+				if (params.get("extra") && params.get("extra").trim() !== "") {
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = ssm.extra;
+				}
+			}
 			if (ssm.net == "h2") {
 			if (ssm.net == "h2") {
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = ssm.host;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = ssm.host;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = ssm.path;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = ssm.path;
@@ -352,6 +374,7 @@ function import_ssr_url(btn, urlname, sid) {
 			if (ssm.tls == "tls") {
 			if (ssm.tls == "tls") {
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_alpn')[0].value = ssm.alpn;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host;
 			}
 			}
 			if (ssm.mux !== undefined) {
 			if (ssm.mux !== undefined) {
@@ -412,6 +435,7 @@ function import_ssr_url(btn, urlname, sid) {
 				setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
 				setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
 				dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
 				dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
 
 
+				setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_alpn', params.get("alpn") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
 			}
 			}
@@ -434,6 +458,18 @@ function import_ssr_url(btn, urlname, sid) {
 				}
 				}
 				setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
 				setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
 				break;
 				break;
+			case "xhttp":
+				if (params.get("security") !== "tls") {
+					setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
+				}
+				setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.get("mode") || "auto");
+				setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
+				if (params.get("extra") && params.get("extra").trim() !== "") {
+					setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true
+					dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件
+					setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.get("extra") || "");
+				}
+				break;
 			case "kcp":
 			case "kcp":
 				setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
 				setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
 				setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");

+ 27 - 6
luci-app-ssr-plus/po/zh_Hans/ssr-plus.po

@@ -901,14 +901,14 @@ msgstr "Socks 协议的认证方式,默认值:noauth。"
 msgid "Socks5 User"
 msgid "Socks5 User"
 msgstr "Socks5 用户名"
 msgstr "Socks5 用户名"
 
 
-msgid "Only when auth is password valid, Mandatory."
-msgstr "仅当 auth 为 password 时有效,必填。"
+msgid "Only when Socks5 Auth Mode is password valid, Mandatory."
+msgstr "仅当 Socks5 认证方式为 Password 时有效,必填。"
 
 
 msgid "Socks5 Password"
 msgid "Socks5 Password"
 msgstr "Socks5 密码"
 msgstr "Socks5 密码"
 
 
-msgid "Only when auth is password valid, Not mandatory."
-msgstr "仅当 auth 为 password 时有效,非必填。"
+msgid "Only when Socks5 Auth Mode is password valid, Not mandatory."
+msgstr "仅当 Socks5 认证方式为 Password 时有效,非必填。"
 
 
 msgid "Enabled Mixed"
 msgid "Enabled Mixed"
 msgstr "启用 Mixed"
 msgstr "启用 Mixed"
@@ -952,8 +952,8 @@ msgstr "UDP 噪声,在某些情况下可以绕过一些针对 UDP 协议的限
 msgid "To send noise packets, select \"Noise\" in Xray Settings."
 msgid "To send noise packets, select \"Noise\" in Xray Settings."
 msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。"
 msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。"
 
 
-msgid "For specific usage, see: "
-msgstr "具体使用方法参见:"
+msgid "For specific usage, see:"
+msgstr "具体使用方法,请参见:"
 
 
 msgid "Click to the page"
 msgid "Click to the page"
 msgstr "点击前往"
 msgstr "点击前往"
@@ -1018,6 +1018,27 @@ msgstr "SplitHTTP 主机名"
 msgid "Splithttp Path"
 msgid "Splithttp Path"
 msgstr "SplitHTTP 路径"
 msgstr "SplitHTTP 路径"
 
 
+msgid "XHTTP Mode"
+msgstr "XHTTP 模式"
+
+msgid "XHTTP Host"
+msgstr "XHTTP 主机名"
+
+msgid "XHTTP Path"
+msgstr "XHTTP 路径"
+
+msgid "XHTTP Extra"
+msgstr "XHTTP 附加项"
+
+msgid "Enable this option to configure XHTTP Extra (JSON format)."
+msgstr "启用此选项配置 XHTTP 附加项(JSON 格式)。"
+
+msgid "Configure XHTTP Extra Settings (JSON format), see:"
+msgstr "配置 XHTTP 额外设置(JSON 格式),请参见:"
+
+msgid "Invalid JSON format"
+msgstr "无效的 JSON 格式"
+
 msgid "HTTP/2 Host"
 msgid "HTTP/2 Host"
 msgstr "HTTP/2 主机名"
 msgstr "HTTP/2 主机名"
 
 

+ 20 - 5
luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua

@@ -4,7 +4,7 @@ local ucursor = require "luci.model.uci".cursor()
 local json = require "luci.jsonc"
 local json = require "luci.jsonc"
 
 
 local server_section = arg[1]
 local server_section = arg[1]
-local proto = arg[2]
+local proto = arg[2] or "tcp"
 local local_port = arg[3] or "0"
 local local_port = arg[3] or "0"
 local socks_port = arg[4] or "0"
 local socks_port = arg[4] or "0"
 
 
@@ -179,7 +179,7 @@ end
 
 
 	-- 开启 socks 代理
 	-- 开启 socks 代理
 	-- 检查是否启用 socks 代理
 	-- 检查是否启用 socks 代理
-if proto:find("tcp") and socks_port ~= "0" then
+if proto and proto:find("tcp") and socks_port ~= "0" then
     table.insert(Xray.inbounds, {
     table.insert(Xray.inbounds, {
         -- socks
         -- socks
         protocol = "socks",
         protocol = "socks",
@@ -209,7 +209,7 @@ end
 				security = (server.xtls == '1') and "xtls" or (server.tls == '1') and "tls" or (server.reality == '1') and "reality" or nil,
 				security = (server.xtls == '1') and "xtls" or (server.tls == '1') and "tls" or (server.reality == '1') and "reality" or nil,
 				tlsSettings = (server.tls == '1') and {
 				tlsSettings = (server.tls == '1') and {
 					-- tls
 					-- tls
-					alpn = server.tls_alpn,
+					alpn = (server.transport == "xhttp" and server.xhttp_alpn ~= "") and server.xhttp_alpn or server.tls_alpn,
 					fingerprint = server.fingerprint,
 					fingerprint = server.fingerprint,
 					allowInsecure = (server.insecure == "1"),
 					allowInsecure = (server.insecure == "1"),
 					serverName = server.tls_host,
 					serverName = server.tls_host,
@@ -225,6 +225,7 @@ end
 					minVersion = "1.3"
 					minVersion = "1.3"
 				} or nil,
 				} or nil,
 				realitySettings = (server.reality == '1') and {
 				realitySettings = (server.reality == '1') and {
+					alpn =  (server.transport == "xhttp" and server.xhttp_alpn ~= "") and server.xhttp_alpn or nil,
 					publicKey = server.reality_publickey,
 					publicKey = server.reality_publickey,
 					shortId = server.reality_shortid,
 					shortId = server.reality_shortid,
 					spiderX = server.reality_spiderx,
 					spiderX = server.reality_spiderx,
@@ -271,6 +272,20 @@ end
 					host = (server.splithttp_host or server.tls_host) or nil,
 					host = (server.splithttp_host or server.tls_host) or nil,
 					path = server.splithttp_path or "/"
 					path = server.splithttp_path or "/"
 				} or nil,
 				} or nil,
+				xhttpSettings = (server.transport == "xhttp") and {
+					-- xhttp
+					mode = server.xhttp_mode or "auto",
+					host = (server.xhttp_host or server.tls_host) or nil,
+					path = server.xhttp_path or "/",
+					extra = (server.enable_xhttp_extra == "1" and server.xhttp_extra) and (function()
+						local success, parsed = pcall(json.parse, server.xhttp_extra)
+							if success then
+								return parsed.extra or parsed
+							else
+								return nil
+							end
+						end)() or nil
+				} or nil,
 				httpSettings = (server.transport == "h2") and {
 				httpSettings = (server.transport == "h2") and {
 					-- h2
 					-- h2
 					path = server.h2_path or "",
 					path = server.h2_path or "",
@@ -387,7 +402,7 @@ local ss = {
 	server_port = tonumber(server.server_port),
 	server_port = tonumber(server.server_port),
 	local_address = "0.0.0.0",
 	local_address = "0.0.0.0",
 	local_port = tonumber(local_port),
 	local_port = tonumber(local_port),
-	mode = (proto == "tcp,udp") and "tcp_and_udp" or proto .. "_only",
+	mode = (proto == "tcp,udp") and "tcp_and_udp" or (proto .. "_only"),
 	password = server.password,
 	password = server.password,
 	method = server.encrypt_method_ss,
 	method = server.encrypt_method_ss,
 	timeout = tonumber(server.timeout),
 	timeout = tonumber(server.timeout),
@@ -395,7 +410,7 @@ local ss = {
 	reuse_port = true
 	reuse_port = true
 }
 }
 local hysteria = {
 local hysteria = {
-	server = (server.server_port and (server.port_range and (server.server .. ":" .. server.server_port .. "," .. server.port_range) or server.server .. ":" .. server.server_port) or (server.port_range and server.server .. ":" .. server.port_range or server.server .. ":443")),
+	server = (server.server_port and (server.port_range and (server.server .. ":" .. server.server_port .. "," .. server.port_range) or (server.server .. ":" .. server.server_port) or (server.port_range and server.server .. ":" .. server.port_range or server.server .. ":443"))),
 	bandwidth = (server.uplink_capacity or server.downlink_capacity) and {
 	bandwidth = (server.uplink_capacity or server.downlink_capacity) and {
 	up = tonumber(server.uplink_capacity) and tonumber(server.uplink_capacity) .. " mbps" or nil,
 	up = tonumber(server.uplink_capacity) and tonumber(server.uplink_capacity) .. " mbps" or nil,
 	down = tonumber(server.downlink_capacity) and tonumber(server.downlink_capacity) .. " mbps" or nil 
 	down = tonumber(server.downlink_capacity) and tonumber(server.downlink_capacity) .. " mbps" or nil 

+ 61 - 0
luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua

@@ -193,6 +193,24 @@ local function processData(szType, content)
 			result.splithttp_host = info.host
 			result.splithttp_host = info.host
 			result.splithttp_path = info.path
 			result.splithttp_path = info.path
 		end
 		end
+		if info.net == 'xhttp' then
+			result.xhttp_mode = info.mode
+			result.xhttp_host = info.host
+			result.xhttp_path = info.path
+			-- 检查 extra 参数是否存在且非空
+			result.enable_xhttp_extra = (info.extra and info.extra ~= "") and "1" or nil
+			result.xhttp_extra = (info.extra and info.extra ~= "") and info.extra or nil
+			-- 尝试解析 JSON 数据
+			local success, Data = pcall(jsonParse, info.extra)
+			if success and Data then
+				local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
+					or (Data.downloadSettings and Data.downloadSettings.address)
+				result.download_address = address and address ~= "" and address or nil
+			else
+				-- 如果解析失败,清空下载地址
+				result.download_address = nil
+			end
+		end
 		if info.net == 'h2' then
 		if info.net == 'h2' then
 			result.h2_host = info.host
 			result.h2_host = info.host
 			result.h2_path = info.path
 			result.h2_path = info.path
@@ -231,6 +249,9 @@ local function processData(szType, content)
 		end
 		end
 		if info.tls == "tls" or info.tls == "1" then
 		if info.tls == "tls" or info.tls == "1" then
 			result.tls = "1"
 			result.tls = "1"
+			if info.alpn and info.alpn ~= "" then
+				result.xhttp_alpn = info.alpn
+			end
 			if info.sni and info.sni ~= "" then
 			if info.sni and info.sni ~= "" then
 				result.tls_host = info.sni
 				result.tls_host = info.sni
 			elseif info.host then
 			elseif info.host then
@@ -353,6 +374,11 @@ local function processData(szType, content)
 				local t = split(v, '=')
 				local t = split(v, '=')
 				params[t[1]] = t[2]
 				params[t[1]] = t[2]
 			end
 			end
+			if params.alpn then
+				-- 处理 alpn 参数
+				result.xhttp_alpn = params.alpn
+			end
+
 			if params.sni then
 			if params.sni then
 				-- 未指定peer(sni)默认使用remote addr
 				-- 未指定peer(sni)默认使用remote addr
 				result.tls_host = params.sni
 				result.tls_host = params.sni
@@ -385,6 +411,23 @@ local function processData(szType, content)
 			elseif result.transport == "splithttp" then
 			elseif result.transport == "splithttp" then
 				result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
 				result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
 				result.splithttp_path = params.path and UrlDecode(params.path) or "/"
 				result.splithttp_path = params.path and UrlDecode(params.path) or "/"
+			elseif result.transport == "xhttp" then
+				result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
+				result.xhttp_mode = params.mode or "auto"
+				result.xhttp_path = params.path and UrlDecode(params.path) or "/"
+				-- 检查 extra 参数是否存在且非空
+				result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
+				result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
+				-- 尝试解析 JSON 数据
+				local success, Data = pcall(jsonParse, params.extra)
+				if success and Data then
+					local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
+						or (Data.downloadSettings and Data.downloadSettings.address)
+					result.download_address = address and address ~= "" and address or nil
+				else
+					-- 如果解析失败,清空下载地址
+					result.download_address = nil
+				end
 			elseif result.transport == "http" or result.transport == "h2" then
 			elseif result.transport == "http" or result.transport == "h2" then
 				result.transport = "h2"
 				result.transport = "h2"
 				result.h2_host = params.host and UrlDecode(params.host) or nil
 				result.h2_host = params.host and UrlDecode(params.host) or nil
@@ -426,6 +469,7 @@ local function processData(szType, content)
 		result.vless_encryption = params.encryption or "none"
 		result.vless_encryption = params.encryption or "none"
 		result.transport = params.type or "tcp"
 		result.transport = params.type or "tcp"
 		result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0"
 		result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0"
+		result.xhttp_alpn = params.alpn or ""
 		result.tls_host = params.sni
 		result.tls_host = params.sni
 		result.tls_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil
 		result.tls_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil
 		result.fingerprint = params.fp
 		result.fingerprint = params.fp
@@ -442,6 +486,23 @@ local function processData(szType, content)
 		elseif result.transport == "splithttp" then
 		elseif result.transport == "splithttp" then
 			result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
 			result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
 			result.splithttp_path = params.path and UrlDecode(params.path) or "/"
 			result.splithttp_path = params.path and UrlDecode(params.path) or "/"
+		elseif result.transport == "xhttp" then
+			result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
+			result.xhttp_mode = params.mode or "auto"
+			result.xhttp_path = params.path and UrlDecode(params.path) or "/"
+			-- 检查 extra 参数是否存在且非空
+			result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
+			result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
+			-- 尝试解析 JSON 数据
+			local success, Data = pcall(jsonParse, params.extra)
+			if success and Data then
+				local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
+					or (Data.downloadSettings and Data.downloadSettings.address)
+				result.download_address = address and address ~= "" and address or nil
+			else
+				-- 如果解析失败,清空下载地址
+				result.download_address = nil
+			end
 		-- make it compatible with bullshit, "h2" transport is non-existent at all
 		-- make it compatible with bullshit, "h2" transport is non-existent at all
 		elseif result.transport == "http" or result.transport == "h2" then
 		elseif result.transport == "http" or result.transport == "h2" then
 			result.transport = "h2"
 			result.transport = "h2"