diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
index 88351ff0ac2..6bd7aaafb33 100644
--- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
+++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
@@ -204,12 +204,12 @@ for key, server_type in pairs(type_table) do
end
-- 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:depends("socks5_auth", "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.rmempty = true
o:depends("socks5_auth", "password")
@@ -263,7 +263,7 @@ o.default = 0
s = m:section(TypedSection, "xray_noise_packets", translate("Xray Noise Packets"))
s.description = translate(
"" .. translate("To send noise packets, select \"Noise\" in Xray Settings.") .. "" ..
- "
" .. translate("For specific usage, see: ") .. "" ..
+ "
" .. translate("For specific usage, see:") .. "" ..
"" ..
"" .. translate("Click to the page") .. "")
s.template = "cbi/tblsection"
diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
index c858b951572..5504de197e1 100644
--- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
+++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua
@@ -4,6 +4,7 @@
require "nixio.fs"
require "luci.sys"
require "luci.http"
+require "luci.jsonc"
require "luci.model.ipkg"
local m, s, o
@@ -504,7 +505,6 @@ o.rmempty = true
o.default = ""
o:depends("type", "tuic")
-
o = s:option(ListValue, "udp_relay_mode", translate("UDP relay mode"))
o:depends("type", "tuic")
o:value("native", translate("native UDP characteristics"))
@@ -623,6 +623,7 @@ o:value("kcp", "mKCP")
o:value("ws", "WebSocket")
o:value("httpupgrade", "HTTPUpgrade")
o:value("splithttp", "SplitHTTP")
+o:value("xhttp", "XHTTP")
o:value("h2", "HTTP/2")
o:value("quic", "QUIC")
o:value("grpc", "gRPC")
@@ -703,6 +704,78 @@ o = s:option(Value, "splithttp_path", translate("Splithttp Path"))
o:depends("transport", "splithttp")
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(
+ "" .. translate("Configure XHTTP Extra Settings (JSON format), see:") .. "" ..
+ " " ..
+ "" .. translate("Click to the page") .. "")
+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域名
diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
index 2607fb70848..d8dfbdd7784 100644
--- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
+++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
@@ -263,6 +263,18 @@
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
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":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
@@ -338,6 +350,16 @@
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = ssm.host;
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") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = ssm.host;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = ssm.path;
@@ -352,6 +374,7 @@
if (ssm.tls == "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
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;
}
if (ssm.mux !== undefined) {
@@ -412,6 +435,7 @@
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
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 + '.tls_host', params.get("sni") || "");
}
@@ -434,6 +458,18 @@
}
setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
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":
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
diff --git a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
index 9992e8173d0..03541f45c94 100644
--- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
+++ b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
@@ -901,14 +901,14 @@ msgstr "Socks 协议的认证方式,默认值:noauth。"
msgid "Socks5 User"
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"
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"
msgstr "启用 Mixed"
@@ -952,8 +952,8 @@ msgstr "UDP 噪声,在某些情况下可以绕过一些针对 UDP 协议的限
msgid "To send noise packets, select \"Noise\" in Xray Settings."
msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。"
-msgid "For specific usage, see: "
-msgstr "具体使用方法参见:"
+msgid "For specific usage, see:"
+msgstr "具体使用方法,请参见:"
msgid "Click to the page"
msgstr "点击前往"
@@ -1018,6 +1018,27 @@ msgstr "SplitHTTP 主机名"
msgid "Splithttp Path"
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"
msgstr "HTTP/2 主机名"
diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua
index 8210ac36096..9c50aa5f081 100755
--- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua
+++ b/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 server_section = arg[1]
-local proto = arg[2]
+local proto = arg[2] or "tcp"
local local_port = arg[3] or "0"
local socks_port = arg[4] or "0"
@@ -179,7 +179,7 @@ end
-- 开启 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, {
-- 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,
tlsSettings = (server.tls == '1') and {
-- tls
- alpn = server.tls_alpn,
+ alpn = (server.transport == "xhttp" and server.xhttp_alpn ~= "") and server.xhttp_alpn or server.tls_alpn,
fingerprint = server.fingerprint,
allowInsecure = (server.insecure == "1"),
serverName = server.tls_host,
@@ -225,6 +225,7 @@ end
minVersion = "1.3"
} or nil,
realitySettings = (server.reality == '1') and {
+ alpn = (server.transport == "xhttp" and server.xhttp_alpn ~= "") and server.xhttp_alpn or nil,
publicKey = server.reality_publickey,
shortId = server.reality_shortid,
spiderX = server.reality_spiderx,
@@ -271,6 +272,20 @@ end
host = (server.splithttp_host or server.tls_host) or nil,
path = server.splithttp_path or "/"
} 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 {
-- h2
path = server.h2_path or "",
@@ -387,7 +402,7 @@ local ss = {
server_port = tonumber(server.server_port),
local_address = "0.0.0.0",
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,
method = server.encrypt_method_ss,
timeout = tonumber(server.timeout),
@@ -395,7 +410,7 @@ local ss = {
reuse_port = true
}
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 {
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
diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua
index 777923a18b6..c4ad4768abf 100755
--- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua
+++ b/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_path = info.path
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
result.h2_host = info.host
result.h2_path = info.path
@@ -231,6 +249,9 @@ local function processData(szType, content)
end
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
+ if info.alpn and info.alpn ~= "" then
+ result.xhttp_alpn = info.alpn
+ end
if info.sni and info.sni ~= "" then
result.tls_host = info.sni
elseif info.host then
@@ -353,6 +374,11 @@ local function processData(szType, content)
local t = split(v, '=')
params[t[1]] = t[2]
end
+ if params.alpn then
+ -- 处理 alpn 参数
+ result.xhttp_alpn = params.alpn
+ end
+
if params.sni then
-- 未指定peer(sni)默认使用remote addr
result.tls_host = params.sni
@@ -385,6 +411,23 @@ local function processData(szType, content)
elseif result.transport == "splithttp" then
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 "/"
+ 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
result.transport = "h2"
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.transport = params.type or "tcp"
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_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil
result.fingerprint = params.fp
@@ -442,6 +486,23 @@ local function processData(szType, content)
elseif result.transport == "splithttp" then
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 "/"
+ 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
elseif result.transport == "http" or result.transport == "h2" then
result.transport = "h2"