Files
2026-10-07 18:47:42 +08:00

51 lines
3.0 KiB
Lua

#!/usr/bin/lua
-- Run on OpenWrt with the installed Lua dependencies; reads UCI, never updates it.
-- lua subscribe-ss.lua /path/to/subscribe.lua
local path = arg[1] or "/usr/share/shadowsocksr/subscribe.lua"
local file = assert(io.open(path))
local source = file:read("*a")
file:close()
source = source:gsub("^#![^\n]*", "")
local boundary = assert(source:find("\nlocal function md5_string", 1, true))
-- Load only the parser definitions, stopping before subscription execution.
local parse = assert(loadstring(source:sub(1, boundary - 1) .. [[
has_xray, has_ss_rust, has_mihomo = true, false, false
return processData
]], "@" .. path))()
local b64 = require("nixio").bin.b64encode
local count = 0
local function check(label, link, expected)
local ok, node = pcall(parse, "ss", link)
assert(ok, label .. ": parser raised an error")
if expected then
assert(node, label .. ": node rejected")
assert(node.type == "v2ray" and node.v2ray_protocol == "shadowsocks", label .. ": wrong backend")
for key, value in pairs(expected) do
assert(tostring(node[key]) == tostring(value), label .. ": incorrect " .. key)
end
else
assert(node == nil, label .. ": malformed node accepted")
end
count = count + 1
end
local expected = {server="ss.example.com", server_port=20070, encrypt_method_ss="aes-256-gcm", password="test-password", alias="乌克兰W01"}
local suffix = "#%E4%B9%8C%E5%85%8B%E5%85%B0W01"
local full = b64("aes-256-gcm:test-password@ss.example.com:20070")
check("whole-link base64", full .. suffix, expected)
check("unpadded whole-link base64", full:gsub("=+$", "") .. suffix, expected)
check("SIP002", b64("aes-256-gcm:test-password") .. "@ss.example.com:20070" .. suffix, expected)
check("plaintext credentials", "aes-256-gcm:test-password@ss.example.com:20070" .. suffix, expected)
local password = "p@ss:word+/%?#"
local credentials = "aes-256-gcm:" .. password
for _,link in ipairs({b64(credentials .. "@ss.example.com:20070"), b64(credentials) .. "@ss.example.com:20070", b64(credentials):gsub("+", "-"):gsub("/", "_"):gsub("=+$", "") .. "@ss.example.com:20070"}) do
check("reserved password characters", link, {password=password, server="ss.example.com", server_port=20070})
end
check("percent-encoded plaintext", "aes-256-gcm:p%40ss%3Aword+%2F%25%3F%23@ss.example.com:20070", {password=password})
check("legacy query", full .. "?type=ws&host=cdn.example.com&path=%2Fsocket&security=tls&sni=tls.example.com", {transport="ws", ws_path="/socket", tls="1", tls_host="tls.example.com"})
check("UUID extension", "12345678-1234-1234-1234-123456789abc@ss.example.com:443?encryption=none&type=ws", {password="12345678-1234-1234-1234-123456789abc", encrypt_method_ss="none", transport="ws"})
check("IPv6", b64("aes-256-gcm:test-password@[2001:db8::1]:443"), {server="2001:db8::1", server_port=443})
for _,link in ipairs({"", "invalid", b64("missing-separator"), b64("aes-256-gcm:password@ss.example.com"), "aes-256-gcm:password@:443", b64(":password@ss.example.com:443")}) do
check("malformed input", link, nil)
end
print("SS subscription parser: " .. count .. " cases passed")