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 <noreply@anthropic.com>
This commit is contained in:
Wei-Lun Li
2026-11-03 20:44:03 +09:00
committed by GitHub
parent fe54a73d74
commit ff9fd7b988
4 changed files with 99 additions and 2 deletions
@@ -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();
}
}
@@ -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();
}
}
+4 -1
View File
@@ -17,7 +17,10 @@ public class AnytlsFmt : BaseFmt
ConfigType = EConfigType.Anytls, ConfigType = EConfigType.Anytls,
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost, 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); var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Password = rawUserInfo; item.Password = rawUserInfo;
+4 -1
View File
@@ -17,7 +17,10 @@ public class NaiveFmt : BaseFmt
ConfigType = EConfigType.Naive, ConfigType = EConfigType.Naive,
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost, 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(); var protocolExtra = item.GetProtocolExtra();
if (parsedUrl.Scheme.Contains("quic")) if (parsedUrl.Scheme.Contains("quic"))