public string Encode(EncodeOptions options, Server[] servers, out Server[] encodedServers) { var encodedServerList = new List <Server>(); var sb = new StringBuilder(); sb.AppendLine($"REMARKS={options.ProfileName}"); sb.AppendLine(""); foreach (var server in servers) { var serverLine = server switch { ShadowsocksServer ss => EncodeShadowsocksServer(ss), ShadowsocksRServer ssr => EncodeShadowsocksRServer(ssr), _ => null, }; if (string.IsNullOrWhiteSpace(serverLine)) { this.logger.LogInformation($"Server {server} is ignored."); } else { encodedServerList.Add(server); sb.AppendLine(serverLine); } } encodedServers = encodedServerList.ToArray(); return(Convert.ToBase64String(Encoding.ASCII.GetBytes(sb.ToString()))); }
public string EncodeProxyList(Server[] servers, out Server[] encodedServers) { var encodedServersList = new List <Server>(); var serverLines = servers .Select(server => { string serverLine = server switch { ShadowsocksServer ssServer => this.EncodeShadowsocksServer(ssServer), ShadowsocksRServer ssrServer => this.EncodeShadowsocksRServer(ssrServer), _ => null, }; if (!string.IsNullOrWhiteSpace(serverLine)) { encodedServersList.Add(server); } else { this.logger.LogInformation($"Server {server} is ignored."); } return(serverLine); }) .Where(line => !string.IsNullOrWhiteSpace(line)) .ToArray(); encodedServers = encodedServersList.ToArray(); return(string.Join('\n', serverLines)); }
private string EncodeShadowsocksServer(ShadowsocksServer server, string groupName = null) { var userInfo = WebSafeBase64Encode(Encoding.UTF8.GetBytes($"{server.Method}:{server.Password}")); var uri = new UriBuilder(); var queryBuilder = new QueryBuilder(); uri.UserName = userInfo; uri.Host = server.Host; uri.Port = server.Port; uri.Path = "/"; uri.Fragment = HttpUtility.UrlEncode(server.Name).Replace("+", "%20"); uri.Scheme = "ss"; if (server.PluginOptions is SimpleObfsPluginOptions options) { var obfsHost = string.IsNullOrEmpty(options.Host) ? Constant.ObfsucationHost : options.Host; queryBuilder.Add("plugin", $"obfs-local;obfs={options.Mode.ToString().ToLower()};obfs-host={obfsHost}"); } if (!string.IsNullOrEmpty(groupName)) { queryBuilder.Add("name", WebSafeBase64Encode(Encoding.UTF8.GetBytes(groupName))); } uri.Query = queryBuilder.ToString().Replace(";", "%3B"); return(uri.ToString()); }
/// <summary> /// Follow scheme https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html /// to parse a given shadowsocks server uri. /// </summary> internal ShadowsocksServer ParseShadowsocksURI(string ssURI) { var u = new Uri(ssURI); var server = new ShadowsocksServer(); // UserInfo: WebSafe Base64(method:password) var plainUserInfo = Encoding.UTF8.GetString(WebSafeBase64Decode(HttpUtility.UrlDecode(u.UserInfo))).Split(':', 2); server.Method = plainUserInfo[0]; server.Password = plainUserInfo.ElementAtOrDefault(1); // Host & Port server.Host = u.Host; server.Port = u.Port; // Name if (u.Fragment.StartsWith("#")) { server.Name = HttpUtility.UrlDecode(u.Fragment.Substring(1)); } // Plugin var otherParams = HttpUtility.ParseQueryString(u.Query); var plugin = (otherParams.Get("plugin") ?? "").Trim(); var pluginInfos = plugin.Split(";"); switch (pluginInfos[0].Trim()) { case "simple-obfs": case "obfs-local": var options = new SimpleObfsPluginOptions(); server.PluginOptions = options; foreach (var info in pluginInfos) { var trimedInfo = info.Trim(); if (trimedInfo.StartsWith("obfs=")) { if (SimpleObfsPluginOptions.TryParseMode(trimedInfo.Substring("obfs=".Length).Trim(), out SimpleObfsPluginMode mode)) { options.Mode = mode; } } else if (string.IsNullOrEmpty(options.Host) && trimedInfo.StartsWith("obfs-host=")) { options.Host = trimedInfo.Substring("obfs-host=".Length); } else { // TODO: log } } break; } return(server); }
public string EncodeShadowsocksServer(ShadowsocksServer server) { var properties = new List <string> { $"shadowsocks={server.Host}:{server.Port}", $"method={server.Method}", $"password={server.Password}", }; switch (server.PluginOptions) { case V2RayPluginOptions options: if (options.Mode == V2RayPluginMode.QUIC) { // v2ray-plugin with QUIC is not supported return(null); } else if (options.Mode == V2RayPluginMode.WebSocket) { properties.Add("obfs=" + (options.EnableTLS ? "wss" : "ws")); properties.Add($"obfs-host=" + (string.IsNullOrWhiteSpace(options.Host) ? Constant.ObfsucationHost : options.Host)); if (!string.IsNullOrWhiteSpace(options.Path) || options.Path == "/") { properties.Add($"obfs-uri={options.Path}"); } } break; case SimpleObfsPluginOptions options: properties.Add($"obfs={options.Mode.ToString().ToLower()}"); properties.Add($"obfs-host=" + (string.IsNullOrWhiteSpace(options.Host) ? Constant.ObfsucationHost : options.Host)); if (options.Mode == SimpleObfsPluginMode.HTTP) { properties.Add($"obfs-uri={options.Uri}"); } break; } properties.Add($"fast-open={server.FastOpen.ToString().ToLower()}"); properties.Add($"udp-relay={server.UDPRelay.ToString().ToLower()}"); properties.Add($"tag={server.Name}"); return(string.Join(", ", properties)); }
private string EncodeShadowsocksServer(ShadowsocksServer server) { var userInfo = WebSafeBase64Encode(Encoding.UTF8.GetBytes($"{server.Method}:{server.Password}")); var uri = new UriBuilder(); uri.UserName = userInfo; uri.Host = server.Host; uri.Port = server.Port; uri.Path = "/"; uri.Fragment = HttpUtility.UrlEncode(server.Name).Replace("+", "%20"); uri.Scheme = "ss"; if (server.PluginType == PluginType.SimpleObfs) { var options = server.PluginOptions as SimpleObfsPluginOptions; var obfsHost = String.IsNullOrEmpty(options.Host) ? Constant.ObfsucationHost : options.Host; uri.Query = "plugin=obfs-local" + HttpUtility.UrlEncode($";obfs={options.Mode};obfs-host={obfsHost}"); } return(uri.ToString()); }
public Server[] ParseProxyList(string profile) { var servers = new List <Server>(); var plainProxies = profile.Trim().Split("\n"); foreach (var proxy in plainProxies) { var trimed = proxy.Trim(); if (trimed.StartsWith("#")) { continue; } var match = proxyRegex.Match(proxy); if (!match.Success) { continue; } var server = new ShadowsocksServer(); server.Name = match.Groups[1].Value; var serverInfos = match.Groups[2].Value.Trim().Split(","); switch (serverInfos[0].Trim().ToLower()) { case "ss": case "custom": break; default: continue; } int port = 0; if (Int32.TryParse((serverInfos[2] ?? ""), out port)) { server.Port = port; } server.Host = (serverInfos[1] ?? "").Trim(); server.Method = TrimPrefix((serverInfos[3] ?? "").Trim(), "encrypt-method="); server.Password = TrimPrefix((serverInfos[4] ?? "").Trim(), "password="******"obfs=")) { pluginOptions.Mode = trimedInfo.Substring("obfs=".Length).TrimStart(); } else if (String.IsNullOrEmpty(pluginOptions.Host) && trimedInfo.StartsWith("obfs-host=")) { pluginOptions.Host = trimedInfo.Substring("obfs-host=".Length).TrimStart(); } else if (!server.UDPRelay && trimedInfo.StartsWith("udp-relay=")) { server.UDPRelay = trimedInfo.Substring("udp-relay=".Length).TrimStart() == "true"; } // module is no more needed for newer version of surge else if (trimedInfo.StartsWith("http://") || trimedInfo.StartsWith("https://")) { continue; } else { logger.LogWarning($"Unsupported surge proxy parameter found: {trimedInfo}"); } } if (!String.IsNullOrEmpty(pluginOptions.Mode)) { server.PluginType = PluginType.SimpleObfs; server.PluginOptions = pluginOptions; } servers.Add(server); } return(servers.ToArray()); }
/// <summary> /// Parse QuantumultX shadowsocks proxy setting /// Sample: /// shadowsocks=example.com:80, method=chacha20, password=pwd, obfs=http, obfs-host=bing.com, obfs-uri=/resource/file, fast-open=false, udp-relay=false, server_check_url=http://www.apple.com/generate_204, tag=ss-01 /// shadowsocks=example.com:443, method=chacha20, password=pwd, ssr-protocol=auth_chain_b, ssr-protocol-param=def, obfs=tls1.2_ticket_fastauth, obfs-host=bing.com, tag=ssr /// </summary> /// <param name="line">A string starts with `shadowsocks=`</param> /// <returns>Could be an instance of <see cref="ShadowsocksRServer"> or <see cref="ShadowsocksServer"></returns> public Server ParseShadowsocksLine(string line) { var properties = new Dictionary <string, string>(); foreach (var kvPair in line.Split(",")) { var parts = kvPair.Split("=", 2); if (parts.Length != 2) { this.logger.LogWarning($"[{{ComponentName}}] Invaild shadowsocks setting: {line}", this.GetType()); return(null); } properties.TryAdd(parts[0].Trim().ToLower(), parts[1].Trim()); } var host = default(string); var port = default(int); var method = properties.GetValueOrDefault("method"); var password = properties.GetValueOrDefault("password"); if (!Misc.SplitHostAndPort(properties.GetValueOrDefault("shadowsocks"), out host, out port)) { this.logger.LogWarning($"[{{ComponentName}}] Host and port are invalid and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } if (string.IsNullOrEmpty(method)) { this.logger.LogWarning($"[{{ComponentName}}] Encryption method is missing and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } if (string.IsNullOrEmpty(password)) { this.logger.LogWarning($"[{{ComponentName}}] Password is missing and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } Server server; if (this.IsShadowsocksRServer(properties)) { var ssrServer = new ShadowsocksRServer { Method = method, }; ssrServer.Obfuscation = properties.GetValueOrDefault("obfs"); ssrServer.ObfuscationParameter = properties.GetValueOrDefault("obfs-host"); ssrServer.Protocol = properties.GetValueOrDefault("ssr-protocol"); ssrServer.ProtocolParameter = properties.GetValueOrDefault("ssr-protocol-param"); // QuantumultX doesn't support following parameter yet // * UDP Port // * UDP over TCP server = ssrServer; } else { var ssServer = new ShadowsocksServer { Method = method, }; if (bool.TryParse(properties.GetValueOrDefault("udp-relay", "false"), out bool udpRelay)) { ssServer.UDPRelay = udpRelay; } if (bool.TryParse(properties.GetValueOrDefault("fast-open", "false"), out bool fastOpen)) { ssServer.FastOpen = fastOpen; } var obfs = properties.GetValueOrDefault("obfs", "").ToLower(); switch (obfs) { case "tls": case "http": { if (!SimpleObfsPluginOptions.TryParseMode(obfs, out SimpleObfsPluginMode mode)) { this.logger.LogWarning($"[{{ComponentName}}] Simple-obfs mode `{obfs}` is not supported and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } var options = new SimpleObfsPluginOptions() { Host = properties.GetValueOrDefault("obfs-host"), Mode = mode, }; if (obfs != "tls") { options.Uri = properties.GetValueOrDefault("obfs-uri"); } ssServer.PluginOptions = options; } break; case "wss": case "ws": { if (!V2RayPluginOptions.TryParseMode(obfs, out V2RayPluginMode mode)) { this.logger.LogWarning($"[{{ComponentName}}] Simple-obfs mode `{obfs}` is not supported and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } var options = new V2RayPluginOptions() { Mode = mode, Host = properties.GetValueOrDefault("obfs-host"), Path = properties.GetValueOrDefault("obfs-uri"), EnableTLS = obfs == "wss", }; ssServer.PluginOptions = options; } break; default: this.logger.LogWarning($"[{{ComponentName}}] Obfuscation `{obfs}` is not supported and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } server = ssServer; } server.Host = host; server.Port = port; server.Name = properties.GetValueOrDefault("tag", $"{server.Host}:{server.Port}"); return(server); }
public Server ParseShadowsocksServer(YamlMappingNode proxy) { string portString = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "port", "0"); int port; if (!int.TryParse(portString, out port)) { this.logger.LogError($"Invalid port: {port}."); return(null); } var server = new ShadowsocksServer() { Port = port, Name = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "name"), Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "server"), Password = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "password"), Method = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "cipher"), UDPRelay = Yaml.GetTruthFromYamlChildrenNode(proxy, "udp"), }; YamlNode pluginOptionsNode; // refer to offical clash to parse plugin options // https://github.com/Dreamacro/clash/blob/34338e7107c1868124f8aab2446f6b71c9b0640f/adapters/outbound/shadowsocks.go#L135 if (proxy.Children.TryGetValue("plugin-opts", out pluginOptionsNode) && pluginOptionsNode.NodeType == YamlNodeType.Mapping) { switch (Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "plugin").ToLower()) { case "obfs": var simpleObfsModeString = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "mode"); var simpleObfsOptions = new SimpleObfsPluginOptions(); if (SimpleObfsPluginOptions.TryParseMode(simpleObfsModeString, out SimpleObfsPluginMode simpleObfsMode)) { simpleObfsOptions.Mode = simpleObfsMode; } else if (!string.IsNullOrWhiteSpace(simpleObfsModeString)) { this.logger.LogError($"Unsupported simple-obfs mode: {simpleObfsModeString}. This server will be ignored."); return(null); } simpleObfsOptions.Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "host"); server.PluginOptions = simpleObfsOptions; break; case "v2ray-plugin": // also refer to official v2ray-plugin to parse v2ray-plugin options // https://github.com/shadowsocks/v2ray-plugin/blob/c7017f45bb1e12cf1e4b739bcb8f42f3eb8b22cd/main.go#L126 var v2rayModeString = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "mode"); var options = new V2RayPluginOptions(); server.PluginOptions = options; if (V2RayPluginOptions.TryParseMode(v2rayModeString, out V2RayPluginMode v2rayMode)) { options.Mode = v2rayMode; } else { this.logger.LogError($"Unsupported v2ray-plugin mode: {v2rayModeString}. This server will be ignored."); return(null); } options.Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "host"); options.Path = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "path"); options.EnableTLS = Yaml.GetTruthFromYamlChildrenNode(pluginOptionsNode, "tls"); options.SkipCertVerification = Yaml.GetTruthFromYamlChildrenNode(pluginOptionsNode, "skip-cert-verify"); options.Headers = new Dictionary <string, string>(); YamlNode headersNode; if (!(pluginOptionsNode as YamlMappingNode).Children.TryGetValue("headers", out headersNode)) { break; } if (headersNode.NodeType != YamlNodeType.Mapping) { break; } foreach (var header in (headersNode as YamlMappingNode)) { if (header.Value.NodeType != YamlNodeType.Scalar) { continue; } options.Headers.Add((header.Key as YamlScalarNode).Value, (header.Value as YamlScalarNode).Value); } break; } } if (server.PluginOptions == null) { var simpleObfsModeString = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "obfs"); if (SimpleObfsPluginOptions.TryParseMode(simpleObfsModeString, out SimpleObfsPluginMode simpleObfsMode)) { server.PluginOptions = new SimpleObfsPluginOptions() { Mode = simpleObfsMode, Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "obfs-host") }; } else if (!string.IsNullOrWhiteSpace(simpleObfsModeString)) { this.logger.LogError($"Unsupported simple-obfs mode: {simpleObfsModeString}"); return(null); } } return(server); }
public static Server ParseShadowsocksServer(YamlMappingNode proxy) { int port; if (!Int32.TryParse(Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "port", "0"), out port)) { return(null); } var server = new ShadowsocksServer() { Port = port, Name = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "name"), Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "server"), Password = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "password"), Method = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "cipher"), UDPRelay = Yaml.GetTruthFromYamlChildrenNode(proxy, "udp"), }; YamlNode pluginOptionsNode; // refer to offical clash to parse plugin options // https://github.com/Dreamacro/clash/blob/34338e7107c1868124f8aab2446f6b71c9b0640f/adapters/outbound/shadowsocks.go#L135 if (proxy.Children.TryGetValue("plugin-opts", out pluginOptionsNode) && pluginOptionsNode.NodeType == YamlNodeType.Mapping) { switch (Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "plugin")) { case "obfs": server.PluginType = PluginType.SimpleObfs; server.PluginOptions = new SimpleObfsPluginOptions() { Mode = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "mode"), Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "host"), }; break; case "v2ray-plugin": // also refer to official v2ray-plugin to parse v2ray-plugin options // https://github.com/shadowsocks/v2ray-plugin/blob/c7017f45bb1e12cf1e4b739bcb8f42f3eb8b22cd/main.go#L126 var options = new V2RayPluginOptions(); server.PluginType = PluginType.V2Ray; server.PluginOptions = options; options.Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "host"); options.Mode = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "mode"); options.Path = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "path"); options.EnableTLS = Yaml.GetTruthFromYamlChildrenNode(pluginOptionsNode, "tls"); options.SkipCertVerification = Yaml.GetTruthFromYamlChildrenNode(pluginOptionsNode, "skip-cert-verify"); options.Headers = new Dictionary <string, string>(); YamlNode headersNode; if (!(pluginOptionsNode as YamlMappingNode).Children.TryGetValue("headers", out headersNode)) { break; } if (headersNode.NodeType != YamlNodeType.Mapping) { break; } foreach (var header in (headersNode as YamlMappingNode)) { if (header.Value.NodeType != YamlNodeType.Scalar) { continue; } options.Headers.Add((header.Key as YamlScalarNode).Value, (header.Value as YamlScalarNode).Value); } break; } } if (server.PluginType == PluginType.None) { var obfs = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "obfs"); var obfsHost = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "obfs-host"); if (!String.IsNullOrEmpty(obfs)) { server.PluginType = PluginType.SimpleObfs; server.PluginOptions = new SimpleObfsPluginOptions() { Mode = obfs, Host = obfsHost }; } } return(server); }
public Server ParseShadowsocksServer(string[] properties) { if (properties.Length < 3) { this.logger.LogError("Invaild surge shadowsocks proxy: " + string.Join(", ", properties)); return(null); } if (!int.TryParse(properties[2].Trim(), out int port)) { this.logger.LogError("Invalid shadowsocks port: ", properties[2].Trim()); return(null); } ; var server = new ShadowsocksServer() { Host = properties[1].Trim(), Port = port, }; var keyValues = properties .Skip(3) .Select(p => p.Trim().Split("=", 2)) .Where(p => p.Length == 2) .Select(p => new string[] { p[0].TrimEnd(), p[1].TrimStart() }); string obfs = null, obfsHost = null; foreach (var keyValue in keyValues) { var key = keyValue[0]; if (key == "psk" || key == "password") { server.Password = keyValue[1]; } else if (key == "encrypt-method") { server.Method = keyValue[1]; } else if (key == "obfs") { obfs = keyValue[1]; } else if (key == "obfs-host") { obfsHost = keyValue[1]; } else if (key == "tfo") { server.FastOpen = keyValue[1] == "true"; } else if (key == "udp-relay") { server.UDPRelay = keyValue[1] == "true"; } else if (key != "interface" && key != "allow-other-interface") { this.logger.LogWarning($"Unrecognized shadowsocks options {key}={keyValue[1]}"); } } if (server.Password == null) { this.logger.LogError("Shadowsocks password is missing!"); return(null); } if (server.Method == null) { this.logger.LogError("Shadowsocks method is missing!"); return(null); } switch (obfs) { case "http": server.PluginOptions = new SimpleObfsPluginOptions() { Mode = SimpleObfsPluginMode.HTTP, Host = obfsHost, }; break; case "tls": server.PluginOptions = new SimpleObfsPluginOptions() { Mode = SimpleObfsPluginMode.TLS, Host = obfsHost, }; break; case null: break; default: this.logger.LogError("Unknown shadowsocks obfusaction mode: " + obfs); return(null); } return(server); }
public ShadowsocksServer ParseSsUri(string text) { var data = new ShadowsocksServer(); text = text.Replace("/?", "?"); if (text.Contains("#")) { data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); text = text.Split('#')[0]; } if (text.Contains("?")) { var finder = new Regex(@"^(?<data>.+?)\?(.+)$"); var match = finder.Match(text); if (!match.Success) { throw new FormatException(); } var plugins = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("plugin")); if (plugins != null) { var plugin = plugins.Substring(0, plugins.IndexOf(";", StringComparison.Ordinal)); var pluginopts = plugins.Substring(plugins.IndexOf(";", StringComparison.Ordinal) + 1); switch (plugin) { case "obfs-local": case "simple-obfs": plugin = "simple-obfs"; if (!pluginopts.Contains("obfs=")) { pluginopts = "obfs=http;obfs-host=" + pluginopts; } break; case "simple-obfs-tls": plugin = "simple-obfs"; if (!pluginopts.Contains("obfs=")) { pluginopts = "obfs=tls;obfs-host=" + pluginopts; } break; } data.Plugin = plugin; data.PluginOption = pluginopts; } text = match.Groups["data"].Value; } if (text.Contains("@")) { var finder = new Regex(@"^ss://(?<base64>.+?)@(?<server>.+):(?<port>\d+)"); var parser = new Regex(@"^(?<method>.+?):(?<password>.+)$"); var match = finder.Match(text); if (!match.Success) { throw new FormatException(); } data.Hostname = match.Groups["server"].Value; data.Port = ushort.Parse(match.Groups["port"].Value); var base64 = ShareLink.URLSafeBase64Decode(match.Groups["base64"].Value); match = parser.Match(base64); if (!match.Success) { throw new FormatException(); } data.EncryptMethod = match.Groups["method"].Value; data.Password = match.Groups["password"].Value; } else { var parser = new Regex(@"^((?<method>.+?):(?<password>.+)@(?<server>.+):(?<port>\d+))"); var match = parser.Match(ShareLink.URLSafeBase64Decode(text.Replace("ss://", ""))); if (!match.Success) { throw new FormatException(); } data.Hostname = match.Groups["server"].Value; data.Port = ushort.Parse(match.Groups["port"].Value); data.EncryptMethod = match.Groups["method"].Value; data.Password = match.Groups["password"].Value; } return(CheckServer(data) ? data : throw new FormatException()); }