From eb713db8672091932b6f0cd57b0260eedd01a661 Mon Sep 17 00:00:00 2001 From: ChsBuffer <33744752+chsbuffer@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:55:46 +0800 Subject: [PATCH] Refactor: generate SS/SSR start arguments --- Netch/Models/ParameterBase.cs | 121 ++++++++++++++++++++ Netch/Servers/Shadowsocks/SSController.cs | 58 ++++++++-- Netch/Servers/ShadowsocksR/SSRController.cs | 73 ++++++++---- UnitTest/ParameterTest.cs | 90 +++++++++++++++ 4 files changed, 310 insertions(+), 32 deletions(-) create mode 100644 Netch/Models/ParameterBase.cs create mode 100644 UnitTest/ParameterTest.cs diff --git a/Netch/Models/ParameterBase.cs b/Netch/Models/ParameterBase.cs new file mode 100644 index 00000000..4ecbb2a5 --- /dev/null +++ b/Netch/Models/ParameterBase.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using System.Reflection; +using Netch.Utils; + +namespace Netch.Models +{ + public abstract class ParameterBase + { + // null value par + + private readonly bool _full; + + protected readonly string ParametersSeparate = " "; + protected readonly string Separate = " "; + protected readonly string VerbPrefix = "-"; + protected readonly string FullPrefix = "--"; + + protected ParameterBase() + { + _full = !GetType().IsDefined(typeof(VerbAttribute)); + } + + public override string ToString() + { + var parameters = GetType().GetProperties().Select(PropToParameter).Where(s => s != null).Cast(); + return string.Join(ParametersSeparate, parameters).Trim(); + } + + private string? PropToParameter(PropertyInfo p) + { + // prefix + bool full; + if (p.IsDefined(typeof(VerbAttribute))) + full = false; + else if (p.IsDefined(typeof(FullAttribute))) + full = true; + else + full = _full; + + var prefix = full ? FullPrefix : VerbPrefix; + // key + var key = p.GetCustomAttribute()?.Name ?? p.Name; + + // build + var value = p.GetValue(this); + switch (value) + { + case bool b: + return b ? $"{prefix}{key}" : null; + default: + if ((value?.ToString() ?? null).IsNullOrWhiteSpace()) + return p.IsDefined(typeof(OptionalAttribute)) ? null : throw new RequiredArgumentValueInvalidException(p.Name, this, null); + + if (p.IsDefined(typeof(QuoteAttribute))) + value = $"\"{value}\""; + + return $"{prefix}{key}{Separate}{value}"; + } + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] + public class VerbAttribute : Attribute + { + // Don't use verb and full both on one class or property + // if you did, will take verb + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] + public class FullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class OptionalAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class QuoteAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class RealNameAttribute : Attribute + { + public string Name { get; } + + public RealNameAttribute(string name) + { + Name = name; + } + } + + [Serializable] + public class RequiredArgumentValueInvalidException : Exception + { + public string? ArgumentName { get; } + + public object? ArgumentObject { get; } + + private readonly string? _message; + + private const string DefaultMessage = "{0}'s Argument \"{1}\" value invalid. A required argument's value can't be null or empty."; + + public override string Message => _message ?? string.Format(DefaultMessage, ArgumentObject!.GetType(), ArgumentName); + + public RequiredArgumentValueInvalidException() + { + _message = "Some Argument value invalid. A required argument value's can't be null or empty."; + } + + public RequiredArgumentValueInvalidException(string argumentName, object argumentObject, string? message) + { + ArgumentName = argumentName; + ArgumentObject = argumentObject; + _message = message; + } + } +} \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/SSController.cs b/Netch/Servers/Shadowsocks/SSController.cs index d1c535cd..ff0337b9 100644 --- a/Netch/Servers/Shadowsocks/SSController.cs +++ b/Netch/Servers/Shadowsocks/SSController.cs @@ -24,26 +24,60 @@ namespace Netch.Servers.Shadowsocks { var server = (Shadowsocks) s; - #region Argument - - var argument = new StringBuilder(); - argument.Append($"-s {server.AutoResolveHostname()} " + $"-p {server.Port} " + $"-b {this.LocalAddress()} " + - $"-l {this.Socks5LocalPort()} " + $"-m {server.EncryptMethod} " + $"-k \"{server.Password}\" " + "-u"); - - if (!string.IsNullOrWhiteSpace(server.Plugin) && !string.IsNullOrWhiteSpace(server.PluginOption)) - argument.Append($" --plugin {server.Plugin}" + $" --plugin-opts \"{server.PluginOption}\""); + var command = new SSParameter + { + s = server.AutoResolveHostname(), + p = server.Port.ToString(), + b = this.LocalAddress(), + l = this.Socks5LocalPort().ToString(), + m = server.EncryptMethod, + k = server.Password, + u = true, + plugin = server.Plugin, + plugin_opts = server.PluginOption + }; if (mode.BypassChina) - argument.Append($" --acl \"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}\""); + command.acl = $"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}"; - #endregion + StartInstanceAuto(command.ToString()); + } - StartInstanceAuto(argument.ToString()); + [Verb] + private class SSParameter : ParameterBase + { + public string? s { get; set; } + + public string? p { get; set; } + + public string? b { get; set; } + + public string? l { get; set; } + + public string? m { get; set; } + + public string? k { get; set; } + + public bool u { get; set; } + + [Full] + [Optional] + public string? plugin { get; set; } + + [Full] + [Optional] + [RealName("plugin-opts")] + public string? plugin_opts { get; set; } + + [Full] + [Quote] + [Optional] + public string? acl { get; set; } } public override void Stop() { - StopInstance(); + StopInstance(); } } } \ No newline at end of file diff --git a/Netch/Servers/ShadowsocksR/SSRController.cs b/Netch/Servers/ShadowsocksR/SSRController.cs index 58263cdc..ace52dff 100644 --- a/Netch/Servers/ShadowsocksR/SSRController.cs +++ b/Netch/Servers/ShadowsocksR/SSRController.cs @@ -24,31 +24,64 @@ namespace Netch.Servers.ShadowsocksR { var server = (ShadowsocksR) s; - #region Argument - - var argument = new StringBuilder(); - argument.Append($"-s {server.AutoResolveHostname()} -p {server.Port} -k \"{server.Password}\" -m {server.EncryptMethod} -t 120"); - if (!string.IsNullOrEmpty(server.Protocol)) + var command = new SSRParameter { - argument.Append($" -O {server.Protocol}"); - if (!string.IsNullOrEmpty(server.ProtocolParam)) - argument.Append($" -G \"{server.ProtocolParam}\""); - } + s = server.AutoResolveHostname(), + p = server.Port.ToString(), + k = server.Password, + m = server.EncryptMethod, + t = "120", + O = server.Protocol, + G = server.ProtocolParam, + o = server.OBFS, + g = server.OBFSParam, + b = this.LocalAddress(), + l = this.Socks5LocalPort().ToString(), + u = true + }; - if (!string.IsNullOrEmpty(server.OBFS)) - { - argument.Append($" -o {server.OBFS}"); - if (!string.IsNullOrEmpty(server.OBFSParam)) - argument.Append($" -g \"{server.OBFSParam}\""); - } - - argument.Append($" -b {this.LocalAddress()} -l {this.Socks5LocalPort()} -u"); if (mode.BypassChina) - argument.Append($" --acl \"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}\""); + command.acl = $"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}"; - #endregion + StartInstanceAuto(command.ToString()); + } - StartInstanceAuto(argument.ToString()); + [Verb] + class SSRParameter : ParameterBase + { + public string? s { get; set; } + + public string? p { get; set; } + + [Quote] + public string? k { get; set; } + + public string? m { get; set; } + + public string? t { get; set; } + + [Optional] + public string? O { get; set; } + + [Optional] + public string? G { get; set; } + + [Optional] + public string? o { get; set; } + + [Optional] + public string? g { get; set; } + + public string? b { get; set; } + + public string? l { get; set; } + + public bool u { get; set; } + + [Full] + [Quote] + [Optional] + public string? acl { get; set; } } public override void Stop() diff --git a/UnitTest/ParameterTest.cs b/UnitTest/ParameterTest.cs new file mode 100644 index 00000000..ef333f29 --- /dev/null +++ b/UnitTest/ParameterTest.cs @@ -0,0 +1,90 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Netch.Models; + +namespace UnitTest +{ + [TestClass] + public class ParameterTest + { + [Verb] + private class VerbAndRealName : ParameterBase + { + [RealName("v")] + public string v1 { get; } = "a"; + + [RealName("v")] + public string v2 { get; } = "b"; + } + + private class Full : ParameterBase + { + [RealName("f")] + public string f1 { get; } = "a"; + + [RealName("f")] + public string f2 { get; } = "b"; + } + + private class FullWithVerb : ParameterBase + { + public string f { get; } = "a"; + + [Verb] + public string v { get; } = "b"; + } + + [Verb] + private class VerbWithFull : ParameterBase + { + public string v { get; } = "a"; + + [Full] + public string f { get; } = "b"; + } + + private class QuoteValue : ParameterBase + { + public static string pathValue = @"C:\Programe Files\Damn thats space"; + + [Quote] + public string path { get; set; } = pathValue; + } + + private class FlagAndOptional : ParameterBase + { + public bool a { get; set; } = true; + + public bool b { get; set; } = false; + + [Optional] + public string c { get; set; } = string.Empty; + + [Optional] + public string? d { get; set; } = null; + } + + private class RequiredEmpty : ParameterBase + { + public string? udp { get; set; } = string.Empty; + } + + private class RequiredNull : ParameterBase + { + public string? udp { get; set; } = null; + } + + [TestMethod] + public void Test() + { + Assert.AreEqual(new VerbAndRealName().ToString(), "-v a -v b"); + Assert.AreEqual(new Full().ToString(), "--f a --f b"); + Assert.AreEqual(new FullWithVerb().ToString(), "--f a -v b"); + Assert.AreEqual(new VerbWithFull().ToString(), "-v a --f b"); + Assert.AreEqual(new QuoteValue().ToString(), $"--path \"{QuoteValue.pathValue}\""); + Assert.AreEqual(new FlagAndOptional().ToString(), "--a"); + Assert.ThrowsException(() => { _ = new RequiredEmpty().ToString(); }); + Assert.ThrowsException(() => { _ = new RequiredNull().ToString(); }); + } + } +} \ No newline at end of file