From 14f2bb5e2c67bc8bd8a72045376b6492b6a1bf8e Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:40:13 +0530 Subject: [PATCH] fix(link): accept padded URL-safe base64 in vmess links (#6975) parseVmess tried standard base64, then unpadded URL-safe base64, so a URL-safe body that kept its = padding failed both and the server silently dropped out of an imported outbound subscription. It now uses base64DecodeFlexible, the helper the shadowsocks parser already uses. --- internal/util/link/outbound.go | 10 +++------- internal/util/link/outbound_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/internal/util/link/outbound.go b/internal/util/link/outbound.go index cda31b192..7980fb7ff 100644 --- a/internal/util/link/outbound.go +++ b/internal/util/link/outbound.go @@ -208,17 +208,13 @@ func dropHostLocalKeys(v any) { func parseVmess(link string) (*ParseResult, error) { b64 := strings.TrimPrefix(link, "vmess://") - // vmess:// base64(json) - raw, err := base64.StdEncoding.DecodeString(padBase64(b64)) - if err != nil { - // Some providers use raw URL-safe - raw, err = base64.RawURLEncoding.DecodeString(b64) - } + // vmess:// base64(json), standard or URL-safe, padded or not + raw, err := base64DecodeFlexible(b64) if err != nil { return nil, fmt.Errorf("vmess decode: %w", err) } var j map[string]any - if err := json.Unmarshal(raw, &j); err != nil { + if err := json.Unmarshal([]byte(raw), &j); err != nil { return nil, fmt.Errorf("vmess json: %w", err) } diff --git a/internal/util/link/outbound_test.go b/internal/util/link/outbound_test.go index 4bf101f12..71722e481 100644 --- a/internal/util/link/outbound_test.go +++ b/internal/util/link/outbound_test.go @@ -24,6 +24,33 @@ func TestParseVmessLink(t *testing.T) { } } +func TestParseVmessLinkBase64Alphabets(t *testing.T) { + raw := []byte(`{"v":"2","ps":"a?>","add":"1.2.3.4","port":443,"id":"uuid","net":"ws","path":"/?","tls":"tls"}`) + if padded := base64.URLEncoding.EncodeToString(raw); !strings.ContainsAny(padded, "-_") || !strings.HasSuffix(padded, "=") { + t.Fatalf("test object must encode to URL-safe characters with padding, got %s", padded) + } + cases := []struct { + name string + b64 string + }{ + {"standard", base64.StdEncoding.EncodeToString(raw)}, + {"standard unpadded", base64.RawStdEncoding.EncodeToString(raw)}, + {"url-safe", base64.URLEncoding.EncodeToString(raw)}, + {"url-safe unpadded", base64.RawURLEncoding.EncodeToString(raw)}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + res, err := ParseLink("vmess://" + c.b64) + if err != nil { + t.Fatalf("parse %s: %v", c.b64, err) + } + if res.Outbound["tag"] != "a?>" { + t.Errorf("tag = %v, want %q", res.Outbound["tag"], "a?>") + } + }) + } +} + func TestLinkIdentityKeepsTLSServerName(t *testing.T) { a, errA := ParseLink("vless://uuid@1.2.3.4:443?type=ws&security=tls&sni=a.example.com#node") b, errB := ParseLink("vless://uuid@1.2.3.4:443?type=ws&security=tls&sni=b.example.com#node")