fix(singbox): keep the ws path query when removing ed (#10548)

Removing "?ed=2048" from a WebSocket path also removed the '?', so
"/ws?ed=2048&key=value" was sent to sing-box as "/ws&key=value".
Match "ed" without the separator in front of it and trim the
leftover '&' or '?' instead.
This commit is contained in:
Aleksey Fedorov
2026-11-08 18:34:30 +10:00
committed by GitHub
parent 5ca37027fd
commit 73670c70d5
2 changed files with 29 additions and 6 deletions
@@ -817,4 +817,29 @@ public class CoreConfigSingboxServiceTests
await proxyOutbound.method.Should().BeEqualTo("aes-128-gcm");
await proxyOutbound.password.Should().BeEqualTo("custom_password");
}
[Test]
[Arguments("/ws?ed=2048", "/ws")]
[Arguments("/ws?ed=2048&key=value", "/ws?key=value")]
[Arguments("/ws?key=value&ed=2048", "/ws?key=value")]
[Arguments("/ws?a=1&ed=2048&b=2", "/ws?a=1&b=2")]
public async Task GenerateClientConfigContent_WsEarlyDataInPath_ShouldKeepRemainingQuery(string path, string expectedPath)
{
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
node.Network = nameof(ETransport.ws);
node.SetTransportExtra(node.GetTransportExtra() with { Path = path });
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box);
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
var transport = cfg.outbounds.First(o => o.tag == Global.ProxyTag).transport;
await transport.Should().NotBeNull();
await transport!.max_early_data.Should().BeEqualTo(2048);
await transport.early_data_header_name.Should().BeEqualTo("Sec-WebSocket-Protocol");
await transport.path.Should().BeEqualTo(expectedPath);
}
}
@@ -542,7 +542,9 @@ public partial class CoreConfigSingboxService
// Parse eh and ed parameters from path using regex
if (!wsPath.IsNullOrEmpty())
{
var edRegex = new Regex(@"[?&]ed=(\d+)");
// Keep the '?' or '&' in front of "ed" out of the match, so removing it
// leaves "/ws?ed=2048&key=value" as "/ws?key=value", not "/ws&key=value".
var edRegex = new Regex(@"(?<=[?&])ed=(\d+)&?");
var edMatch = edRegex.Match(wsPath);
if (edMatch.Success && int.TryParse(edMatch.Groups[1].Value, out var edValue))
{
@@ -550,11 +552,7 @@ public partial class CoreConfigSingboxService
transport.early_data_header_name = "Sec-WebSocket-Protocol";
wsPath = edRegex.Replace(wsPath, "");
wsPath = wsPath.Replace("?&", "?");
if (wsPath.EndsWith('?'))
{
wsPath = wsPath.TrimEnd('?');
}
wsPath = wsPath.TrimEnd('&').TrimEnd('?');
}
var ehRegex = new Regex(@"[?&]eh=([^&]+)");