fix(link): decode share link remarks once (#6977)

url.Parse already unescapes the fragment, and decodeHash unescaped it again, so a + in a vless, trojan, hysteria2 or wireguard remark became a space and an escaped % was decoded twice. These parsers now pass the escaped fragment, decoding it once like the shadowsocks parser and the frontend.
This commit is contained in:
Jay Hemnani
2026-11-02 19:43:54 +05:30
committed by GitHub
parent 4cc742bf6f
commit 4180899f15
2 changed files with 35 additions and 4 deletions
+4 -4
View File
@@ -387,7 +387,7 @@ func parseVless(link string) (*ParseResult, error) {
ob := Outbound{
"protocol": "vless",
"tag": decodeHash(u.Fragment),
"tag": decodeHash(u.EscapedFragment()),
"settings": map[string]any{
"address": host,
"port": port,
@@ -429,7 +429,7 @@ func parseTrojan(link string) (*ParseResult, error) {
ob := Outbound{
"protocol": "trojan",
"tag": decodeHash(u.Fragment),
"tag": decodeHash(u.EscapedFragment()),
"settings": map[string]any{
"servers": []any{
map[string]any{"address": host, "port": port, "password": pw},
@@ -634,7 +634,7 @@ func parseHysteria2(link string) (*ParseResult, error) {
ob := Outbound{
"protocol": "hysteria",
"tag": decodeHash(u.Fragment),
"tag": decodeHash(u.EscapedFragment()),
"settings": map[string]any{"address": host, "port": port, "version": 2},
"streamSettings": stream,
}
@@ -712,7 +712,7 @@ func parseWireguard(link string) (*ParseResult, error) {
ob := Outbound{
"protocol": "wireguard",
"tag": decodeHash(u.Fragment),
"tag": decodeHash(u.EscapedFragment()),
"settings": settings,
}
return &ParseResult{Outbound: ob, Identity: identity}, nil
@@ -430,3 +430,34 @@ func TestParse_VmessFinalMask(t *testing.T) {
t.Fatalf("finalmask.udp = %#v, want %#v", udp, want)
}
}
func TestParse_RemarkDecodedOnce(t *testing.T) {
// Remarks as the subscription server writes them (encodeURIComponent).
cases := []struct {
fragment string
want string
}{
{"VIP%2B", "VIP+"},
{"US%2BEU%201", "US+EU 1"},
{"a%2520b", "a%20b"},
{"%D0%A2%D0%B5%D1%81%D1%82", "Тест"},
}
prefixes := []string{
"vless://uuid@1.2.3.4:443?type=tcp#",
"trojan://pw@1.2.3.4:443#",
"hysteria2://auth@1.2.3.4:443#",
"wireguard://key@1.2.3.4:51820?publickey=pub#",
"ss://YWVzLTI1Ni1nY206cHc@1.2.3.4:8388#",
}
for _, p := range prefixes {
for _, c := range cases {
res, err := ParseLink(p + c.fragment)
if err != nil {
t.Fatalf("parse %s%s: %v", p, c.fragment, err)
}
if res.Outbound["tag"] != c.want {
t.Errorf("%s%s: tag = %q, want %q", p, c.fragment, res.Outbound["tag"], c.want)
}
}
}
}