diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index 41c7a6be..1366f0a1 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -4,12 +4,19 @@ using System.Collections.Specialized; using System.Text; using System.Web; using Shadowsocks.Controller; +using System.Text.RegularExpressions; namespace Shadowsocks.Model { [Serializable] public class Server { + #region ParseLegacyURL + public static readonly Regex + UrlFinder = new Regex(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase), + DetailsParser = new Regex(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase); + #endregion ParseLegacyURL + private const int DefaultServerTimeoutSec = 5; public const int MaxServerTimeoutSec = 20; @@ -70,74 +77,109 @@ namespace Shadowsocks.Model timeout = DefaultServerTimeoutSec; } + private static Server ParseLegacyURL(string ssURL) + { + var match = UrlFinder.Match(ssURL); + if (!match.Success) + return null; + + Server server = new Server(); + var base64 = match.Groups["base64"].Value.TrimEnd('/'); + var tag = match.Groups["tag"].Value; + if (!tag.IsNullOrEmpty()) + { + server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8); + } + Match details = null; + try + { + details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String( + base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')))); + } + catch (FormatException) + { + return null; + } + if (!details.Success) + return null; + server.method = details.Groups["method"].Value; + server.password = details.Groups["password"].Value; + server.server = details.Groups["hostname"].Value; + server.server_port = int.Parse(details.Groups["port"].Value); + return server; + } + public static List GetServers(string ssURL) { - int prefixLength = "ss://".Length; var serverUrls = ssURL.Split('\r', '\n'); List servers = new List(); foreach (string serverUrl in serverUrls) { string _serverUrl = serverUrl.Trim(); - if (!_serverUrl.BeginWith("ss://", StringComparison.InvariantCultureIgnoreCase)) { continue; } - // Decode the Base64 part of Uri - int indexOfHashOrSlash = _serverUrl.IndexOfAny(new[] { '@', '/', '#' }, - prefixLength, _serverUrl.Length - prefixLength); - string webSafeBase64Str = indexOfHashOrSlash == -1 ? - _serverUrl.Substring(prefixLength) : - _serverUrl.Substring(prefixLength, indexOfHashOrSlash - prefixLength); - - string base64Str = webSafeBase64Str.Replace('-', '+').Replace('_', '/'); - string base64 = base64Str.PadRight(base64Str.Length + (4 - base64Str.Length % 4) % 4, '='); - string decodedBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); - string decodedServerUrl = serverUrl.Replace(webSafeBase64Str, decodedBase64); - - Uri parsedUrl; - try + Server legacyServer = ParseLegacyURL(serverUrl); + if (legacyServer != null) //legacy { - parsedUrl = new Uri(decodedServerUrl); + servers.Add(legacyServer); } - catch (UriFormatException) + else //SIP002 { - continue; + Uri parsedUrl; + try + { + parsedUrl = new Uri(serverUrl); + } + catch (UriFormatException) + { + continue; + } + Server server = new Server + { + remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + server = parsedUrl.GetComponents(UriComponents.Host, UriFormat.Unescaped), + server_port = parsedUrl.Port, + }; + + // parse base64 UserInfo + string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); + string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 + string userInfo = ""; + try + { + userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( + base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); + } + catch (FormatException) + { + continue; + } + string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); + if (userInfoParts.Length != 2) + { + continue; + } + server.method = userInfoParts[0]; + server.password = userInfoParts[1]; + + NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); + string[] pluginParts = HttpUtility.UrlDecode(queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); + if (pluginParts.Length > 0) + { + server.plugin = pluginParts[0] ?? ""; + } + + if (pluginParts.Length > 1) + { + server.plugin_opts = pluginParts[1] ?? ""; + } + + servers.Add(server); } - - Server tmp = new Server - { - remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped) - }; - - string userInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); - tmp.server = parsedUrl.GetComponents(UriComponents.Host, UriFormat.Unescaped); - tmp.server_port = parsedUrl.Port; - - string[] userInfoParts = userInfo.Split(new[] { ':' }, 2); - if (userInfoParts.Length != 2) - { - continue; - } - - tmp.method = userInfoParts[0]; - tmp.password = userInfoParts[1]; - - NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); - string[] pluginParts = HttpUtility.UrlDecode(queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); - if (pluginParts.Length > 0) - { - tmp.plugin = pluginParts[0] ?? ""; - } - - if (pluginParts.Length > 1) - { - tmp.plugin_opts = pluginParts[1] ?? ""; - } - - servers.Add(tmp); } return servers; }