diff --git a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs index fd3436db..73a1020a 100644 --- a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs +++ b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs @@ -3,13 +3,11 @@ namespace ServiceLib.Manager; /// /// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.). /// -public class ActionPrecheckManager(Config config) +public class ActionPrecheckManager { - private static readonly Lazy _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config)); + private static readonly Lazy _instance = new(); public static ActionPrecheckManager Instance => _instance.Value; - private readonly Config _config = config; - // sing-box supported transports for different protocol types private static readonly HashSet SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)]; @@ -56,6 +54,7 @@ public class ActionPrecheckManager(Config config) { return []; } + var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); return await ValidateNodeAndCoreSupport(item, coreType); } @@ -71,115 +70,35 @@ public class ActionPrecheckManager(Config config) errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString())); return errors; } - - if (!item.IsComplex()) + else if (item.ConfigType.IsGroupType()) { - if (item.Address.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Address")); - return errors; - } - - if (item.Port is <= 0 or >= 65536) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Port")); - return errors; - } - - switch (item.ConfigType) - { - case EConfigType.VMess: - if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Id")); - } - - break; - - case EConfigType.VLESS: - if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Id")); - } - - if (!Global.Flows.Contains(item.Flow)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); - } - - break; - - case EConfigType.Shadowsocks: - if (item.Id.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Id")); - } - - if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Security")); - } - - break; - } - - if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan - && item.StreamSecurity == Global.StreamSecurityReality - && item.PublicKey.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey")); - } - - if (errors.Count > 0) - { - return errors; - } + 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; } - if (item.ConfigType.IsGroupType()) + return errors; + } + + private async Task> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null) + { + var errors = new List(); + + if (item.Address.IsNullOrEmpty()) { - ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); - if (group is null || group.NotHasChild()) - { - errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks)); - return errors; - } + errors.Add(string.Format(ResUI.InvalidProperty, "Address")); + return errors; + } - var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId); - if (hasCycle) - { - errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks)); - return errors; - } - - var childIds = Utils.String2List(group.ChildItems) ?? []; - var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group); - childIds.AddRange(subItems.Select(p => p.IndexId)); - - foreach (var child in childIds) - { - var childErrors = new List(); - if (child.IsNullOrEmpty()) - { - continue; - } - - var childItem = await AppManager.Instance.GetProfileItem(child); - if (childItem is null) - { - childErrors.Add(string.Format(ResUI.NodeTagNotExist, child)); - continue; - } - - if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain) - { - childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks)); - continue; - } - - childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType)); - errors.AddRange(childErrors); - } + if (item.Port is <= 0 or > 65535) + { + errors.Add(string.Format(ResUI.InvalidProperty, "Port")); return errors; } @@ -191,20 +110,138 @@ public class ActionPrecheckManager(Config config) if (transportError != null) { errors.Add(transportError); - return errors; + } + + 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) - && !item.IsComplex()) + if (!Global.XraySupportConfigType.Contains(item.ConfigType)) { - errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString())); - return errors; + errors.Add(string.Format(ResUI.CoreNotSupportProtocol, + nameof(ECoreType.Xray), item.ConfigType.ToString())); } } + switch (item.ConfigType) + { + case EConfigType.VMess: + if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) + { + errors.Add(string.Format(ResUI.InvalidProperty, "Id")); + } + + break; + + case EConfigType.VLESS: + if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)) + { + errors.Add(string.Format(ResUI.InvalidProperty, "Id")); + } + + if (!Global.Flows.Contains(item.Flow)) + { + errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); + } + + break; + + case EConfigType.Shadowsocks: + if (item.Id.IsNullOrEmpty()) + { + errors.Add(string.Format(ResUI.InvalidProperty, "Id")); + } + + if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security)) + { + errors.Add(string.Format(ResUI.InvalidProperty, "Security")); + } + + break; + } + + if (item.StreamSecurity == Global.StreamSecurity) + { + // check certificate validity + 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")); + } + } + + if (item.Network == nameof(ETransport.xhttp) + && !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")); + } + } + + return errors; + } + + private async Task> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null) + { + var errors = new List(); + + ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); + if (group is null || group.NotHasChild()) + { + errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks)); + return errors; + } + + var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId); + if (hasCycle) + { + errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks)); + return errors; + } + + var childIds = Utils.String2List(group.ChildItems) ?? []; + var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group); + childIds.AddRange(subItems.Select(p => p.IndexId)); + + foreach (var child in childIds) + { + var childErrors = new List(); + if (child.IsNullOrEmpty()) + { + continue; + } + + var childItem = await AppManager.Instance.GetProfileItem(child); + if (childItem is null) + { + childErrors.Add(string.Format(ResUI.NodeTagNotExist, child)); + continue; + } + + if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain) + { + childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks)); + continue; + } + + childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType)); + errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: "))); + } return errors; } @@ -271,7 +308,7 @@ public class ActionPrecheckManager(Config config) if (node is not null) { var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType); - errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s)); + errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s)); } else if (tag.IsNotEmpty()) { @@ -289,7 +326,7 @@ public class ActionPrecheckManager(Config config) } var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config); if (routing == null) { return errors; @@ -317,7 +354,7 @@ public class ActionPrecheckManager(Config config) } var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType); - errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s)); + errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s)); } return errors;