Compare commits

..

No commits in common. "6e27dca6cd833a5742d02ea5f1711f5b1483d56d" and "3885ff8b31d99c82c44059c05b441cd31d0b23cb" have entirely different histories.

9 changed files with 117 additions and 197 deletions

View file

@ -3,11 +3,13 @@ namespace ServiceLib.Manager;
/// <summary> /// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.). /// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// </summary> /// </summary>
public class ActionPrecheckManager public class ActionPrecheckManager(Config config)
{ {
private static readonly Lazy<ActionPrecheckManager> _instance = new(); private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
public static ActionPrecheckManager Instance => _instance.Value; public static ActionPrecheckManager Instance => _instance.Value;
private readonly Config _config = config;
// sing-box supported transports for different protocol types // sing-box supported transports for different protocol types
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)]; private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
@ -54,7 +56,6 @@ public class ActionPrecheckManager
{ {
return []; return [];
} }
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType); return await ValidateNodeAndCoreSupport(item, coreType);
} }
@ -70,64 +71,21 @@ public class ActionPrecheckManager
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString())); errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
return errors; return errors;
} }
else if (item.ConfigType.IsGroupType())
{
var groupErrors = await ValidateGroupNode(item, coreType);
errors.AddRange(groupErrors);
return errors;
}
else if (!item.IsComplex())
{
var normalErrors = await ValidateNormalNode(item, coreType);
errors.AddRange(normalErrors);
return errors;
}
return errors; if (!item.IsComplex())
}
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
{ {
var errors = new List<string>();
if (item.Address.IsNullOrEmpty()) if (item.Address.IsNullOrEmpty())
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Address")); errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors; return errors;
} }
if (item.Port is <= 0 or > 65535) if (item.Port is <= 0 or >= 65536)
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Port")); errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors; return errors;
} }
var net = item.GetNetwork();
if (coreType == ECoreType.sing_box)
{
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
{
errors.Add(transportError);
}
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.Xray), item.ConfigType.ToString()));
}
}
switch (item.ConfigType) switch (item.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
@ -165,54 +123,21 @@ public class ActionPrecheckManager
break; break;
} }
if (item.StreamSecurity == Global.StreamSecurity) if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
{ && item.StreamSecurity == Global.StreamSecurityReality
// check certificate validity && item.PublicKey.IsNullOrEmpty())
if ((!item.Cert.IsNullOrEmpty()) && (CertPemManager.ParsePemChain(item.Cert).Count == 0))
{
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
}
}
if (item.StreamSecurity == Global.StreamSecurityReality)
{
if (item.PublicKey.IsNullOrEmpty())
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey")); errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
} }
}
if (item.Network == nameof(ETransport.xhttp) if (errors.Count > 0)
&& !item.Extra.IsNullOrEmpty())
{ {
// check xhttp extra json validity
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
if (xhttpExtra is null)
{
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
}
}
// ws with tls, tls alpn should contain "http/1.1" in xray core
// rfc6455
// https://github.com/XTLS/Xray-core/blob/81f8f398c7b2b845853b1e85087c6122acc6db0b/transport/internet/tls/tls.go#L95-L116
if (item.Network == nameof(ETransport.ws)
&& item.StreamSecurity == Global.StreamSecurity)
{
var alpnList = Utils.String2List(item.Alpn) ?? [];
if (alpnList.Count > 0 && !alpnList.Contains("http/1.1"))
{
errors.Add(ResUI.AlpnMustContainHttp11ForWsTls);
}
}
return errors; return errors;
} }
}
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null) if (item.ConfigType.IsGroupType())
{ {
var errors = new List<string>();
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild()) if (group is null || group.NotHasChild())
{ {
@ -253,11 +178,36 @@ public class ActionPrecheckManager
} }
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType)); childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: "))); errors.AddRange(childErrors);
} }
return errors; return errors;
} }
var net = item.GetNetwork();
if (coreType == ECoreType.sing_box)
{
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
{
errors.Add(transportError);
return errors;
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
}
}
return errors;
}
private static string? ValidateSingboxTransport(EConfigType configType, string net) private static string? ValidateSingboxTransport(EConfigType configType, string net)
{ {
// sing-box does not support xhttp / kcp transports // sing-box does not support xhttp / kcp transports
@ -321,7 +271,7 @@ public class ActionPrecheckManager
if (node is not null) if (node is not null)
{ {
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType); var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s)); errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
} }
else if (tag.IsNotEmpty()) else if (tag.IsNotEmpty())
{ {
@ -339,7 +289,7 @@ public class ActionPrecheckManager
} }
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config); var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null) if (routing == null)
{ {
return errors; return errors;
@ -367,7 +317,7 @@ public class ActionPrecheckManager
} }
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType); var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s)); errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
} }
return errors; return errors;

View file

@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。 // (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ResUI { public class ResUI {
@ -78,15 +78,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 ALPN must contain &apos;http/1.1&apos; when using WebSocket with TLS. 的本地化字符串。
/// </summary>
public static string AlpnMustContainHttp11ForWsTls {
get {
return ResourceManager.GetString("AlpnMustContainHttp11ForWsTls", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。 /// 查找类似 Export share link to clipboard successfully 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1641,7 +1641,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root> </root>

View file

@ -1638,7 +1638,4 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root> </root>

View file

@ -1641,7 +1641,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root> </root>

View file

@ -1641,7 +1641,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root> </root>

View file

@ -1641,7 +1641,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root> </root>

View file

@ -1638,7 +1638,4 @@
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value> <value>子配置项二,从自建中选择添加</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>使用 WebSocket+TLS 时ALPN 必须包含 'http/1.1'。</value>
</data>
</root> </root>

View file

@ -1638,7 +1638,4 @@
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value> <value>子配置項二,從自建中選擇新增</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
</root> </root>