From 0d9e84556e6cc1a41124c179894b57e0f038df55 Mon Sep 17 00:00:00 2001 From: Wei-Lun Li Date: Wed, 4 Nov 2026 10:34:45 +0900 Subject: [PATCH] fix(fmt): decode the SIP002 plain-text password only once (#10520) For a SIP002 link whose user info is "method:password" rather than base64 (the 2022-blake3 form), ResolveSip002 unescapes the whole user info and then unescapes the password a second time. A password that still holds a valid percent sequence after the first pass decays: p%41ss is written as p%2541ss and imported as pAss. Same double-decoding as #10027 fixed for query values. Co-authored-by: Claude Opus 5.5 --- .../Fmt/ShadowsocksFmtTests.cs | 19 +++++++++++++++++++ .../ServiceLib/Handler/Fmt/ShadowsocksFmt.cs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 v2rayN/ServiceLib.Tests/Fmt/ShadowsocksFmtTests.cs diff --git a/v2rayN/ServiceLib.Tests/Fmt/ShadowsocksFmtTests.cs b/v2rayN/ServiceLib.Tests/Fmt/ShadowsocksFmtTests.cs new file mode 100644 index 00000000..f43cc73e --- /dev/null +++ b/v2rayN/ServiceLib.Tests/Fmt/ShadowsocksFmtTests.cs @@ -0,0 +1,19 @@ +namespace ServiceLib.Tests.Fmt; + +public class ShadowsocksFmtTests +{ + // SIP002 user info that is not base64 is "method:password", percent-encoded once. + [Test] + [Arguments("ss://2022-blake3-aes-128-gcm:p%2541ss@1.2.3.4:8388#demo", "p%41ss")] + [Arguments("ss://2022-blake3-aes-128-gcm:p%25ss@1.2.3.4:8388#demo", "p%ss")] + [Arguments("ss://2022-blake3-aes-128-gcm:a%3Ab%2Fc%2B%3D@1.2.3.4:8388#demo", "a:b/c+=")] + public async Task ResolveConfig_PlainUserInfo_ShouldDecodePasswordOnce(string shareUri, string password) + { + var resolved = FmtHandler.ResolveConfig(shareUri, out var msg); + + await resolved.Should().NotBeNull().Because($"uri: {shareUri}, msg: {msg}"); + await resolved!.ConfigType.Should().BeEqualTo(EConfigType.Shadowsocks); + await resolved.GetProtocolExtra().SsMethod.Should().BeEqualTo("2022-blake3-aes-128-gcm"); + await resolved.Password.Should().BeEqualTo(password); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs index ab805002..a003540a 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs @@ -165,7 +165,7 @@ public class ShadowsocksFmt : BaseFmt return null; } item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() }); - item.Password = Utils.UrlDecode(userInfoParts.Last()); + item.Password = userInfoParts.Last(); } else {