From ff9fd7b988db9c332d2a3d20eca1011925f6beb4 Mon Sep 17 00:00:00 2001 From: Wei-Lun Li Date: Tue, 3 Nov 2026 20:44:03 +0900 Subject: [PATCH] fix(fmt): default the AnyTLS and Naive port to 443 when the link omits it (#10504) The AnyTLS URI scheme makes the port optional and defaults it to 443, and two of its three examples leave it out. The naive+https:// and naive+quic:// link format does the same: the port is omitted when it is 443, and none of its examples carries one. System.Uri answers -1 for an unregistered scheme with no port, so these links were imported with Port = -1 and the profile failed IsValid(). Apply the same default Hysteria2Fmt already uses (#10026). An explicit ":0" is still kept as 0. Co-authored-by: Claude Opus 5.5 --- v2rayN/ServiceLib.Tests/Fmt/AnytlsFmtTests.cs | 47 +++++++++++++++++++ v2rayN/ServiceLib.Tests/Fmt/NaiveFmtTests.cs | 44 +++++++++++++++++ v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs | 5 +- v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs | 5 +- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 v2rayN/ServiceLib.Tests/Fmt/AnytlsFmtTests.cs create mode 100644 v2rayN/ServiceLib.Tests/Fmt/NaiveFmtTests.cs diff --git a/v2rayN/ServiceLib.Tests/Fmt/AnytlsFmtTests.cs b/v2rayN/ServiceLib.Tests/Fmt/AnytlsFmtTests.cs new file mode 100644 index 00000000..f3cc1354 --- /dev/null +++ b/v2rayN/ServiceLib.Tests/Fmt/AnytlsFmtTests.cs @@ -0,0 +1,47 @@ +namespace ServiceLib.Tests.Fmt; + +public class AnytlsFmtTests +{ + // "服务器的地址和可选端口。如果省略端口,则默认为 443。" (the port is optional and defaults to 443) + // -- https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md + // The first two cases are the examples given there. + [Test] + [Arguments("anytls://letmein@example.com/?sni=real.example.com")] + [Arguments("anytls://letmein@example.com/?sni=127.0.0.1&insecure=1")] + [Arguments("anytls://letmein@example.com")] + [Arguments("anytls://letmein@example.com:/")] + public async Task ResolveConfig_WithoutPort_ShouldDefaultTo443(string shareUri) + { + var resolved = FmtHandler.ResolveConfig(shareUri, out var msg); + + await resolved.Should().NotBeNull().Because($"uri: {shareUri}, msg: {msg}"); + await resolved!.ConfigType.Should().BeEqualTo(EConfigType.Anytls); + await resolved.Address.Should().BeEqualTo("example.com"); + await resolved.Port.Should().BeEqualTo(443); + await resolved.Password.Should().BeEqualTo("letmein"); + await resolved.IsValid().Should().BeTrue(); + } + + [Test] + public async Task ResolveConfig_WithExplicitPort_ShouldKeepIt() + { + // The third example from the URI scheme document. + const string shareUri = "anytls://0fdf77d7-d4ba-455e-9ed9-a98dd6d5489a@[2409:8a71:6a00:1953::615]:8964/?insecure=1"; + + var resolved = FmtHandler.ResolveConfig(shareUri, out var msg); + + await resolved.Should().NotBeNull().Because($"uri: {shareUri}, msg: {msg}"); + await resolved!.Address.Should().BeEqualTo("2409:8a71:6a00:1953::615"); + await resolved.Port.Should().BeEqualTo(8964); + } + + [Test] + public async Task ResolveConfig_WithExplicitZeroPort_ShouldNotApplyTheDefault() + { + var resolved = FmtHandler.ResolveConfig("anytls://letmein@example.com:0/", out _); + + await resolved.Should().NotBeNull(); + await resolved!.Port.Should().BeEqualTo(0); + await resolved.IsValid().Should().BeFalse(); + } +} diff --git a/v2rayN/ServiceLib.Tests/Fmt/NaiveFmtTests.cs b/v2rayN/ServiceLib.Tests/Fmt/NaiveFmtTests.cs new file mode 100644 index 00000000..1d1f4694 --- /dev/null +++ b/v2rayN/ServiceLib.Tests/Fmt/NaiveFmtTests.cs @@ -0,0 +1,44 @@ +namespace ServiceLib.Tests.Fmt; + +public class NaiveFmtTests +{ + // "port can be omitted if it's 443" -- https://gist.github.com/DuckSoft/ca03913b0a26fc77a1da4d01cc6ab2f1 + // The first two cases are examples given there; none of its examples carries a port. + [Test] + [Arguments("naive+https://what:happened@test.someone.cf?padding=false#Naive!", false)] + [Arguments("naive+quic://manhole:114514@quic.test.me", true)] + [Arguments("naive+https://what:happened@test.someone.cf/", false)] + [Arguments("naive+https://what:happened@test.someone.cf:/", false)] + public async Task ResolveConfig_WithoutPort_ShouldDefaultTo443(string shareUri, bool quic) + { + var resolved = FmtHandler.ResolveConfig(shareUri, out var msg); + + await resolved.Should().NotBeNull().Because($"uri: {shareUri}, msg: {msg}"); + await resolved!.ConfigType.Should().BeEqualTo(EConfigType.Naive); + await resolved.Port.Should().BeEqualTo(443); + await (resolved.GetProtocolExtra().NaiveQuic == true).Should().BeEqualTo(quic); + await resolved.IsValid().Should().BeTrue(); + } + + [Test] + public async Task ResolveConfig_WithExplicitPort_ShouldKeepIt() + { + var resolved = FmtHandler.ResolveConfig("naive+https://what:happened@test.someone.cf:8443", out _); + + await resolved.Should().NotBeNull(); + await resolved!.Address.Should().BeEqualTo("test.someone.cf"); + await resolved.Port.Should().BeEqualTo(8443); + await resolved.Username.Should().BeEqualTo("what"); + await resolved.Password.Should().BeEqualTo("happened"); + } + + [Test] + public async Task ResolveConfig_WithExplicitZeroPort_ShouldNotApplyTheDefault() + { + var resolved = FmtHandler.ResolveConfig("naive+https://what:happened@test.someone.cf:0", out _); + + await resolved.Should().NotBeNull(); + await resolved!.Port.Should().BeEqualTo(0); + await resolved.IsValid().Should().BeFalse(); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs index 4ea6a0eb..6853a6f4 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs @@ -17,7 +17,10 @@ public class AnytlsFmt : BaseFmt ConfigType = EConfigType.Anytls, Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Address = parsedUrl.IdnHost, - Port = parsedUrl.Port, + // The URI scheme makes the port optional and defaults it to 443 + // (https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md). Uri.Port answers + // -1 for an unregistered scheme carrying no port; an explicit ":0" stays 0. + Port = parsedUrl.Port == -1 ? 443 : parsedUrl.Port, }; var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); item.Password = rawUserInfo; diff --git a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs index 5792472b..5349b205 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs @@ -17,7 +17,10 @@ public class NaiveFmt : BaseFmt ConfigType = EConfigType.Naive, Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Address = parsedUrl.IdnHost, - Port = parsedUrl.Port, + // naive+https:// and naive+quic:// links leave the port out when it is 443 + // (https://gist.github.com/DuckSoft/ca03913b0a26fc77a1da4d01cc6ab2f1). Uri.Port answers + // -1 for an unregistered scheme carrying no port; an explicit ":0" stays 0. + Port = parsedUrl.Port == -1 ? 443 : parsedUrl.Port, }; var protocolExtra = item.GetProtocolExtra(); if (parsedUrl.Scheme.Contains("quic"))