You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Server.cs 9.0 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Specialized;
  4. using System.Text;
  5. using System.Web;
  6. using Shadowsocks.Controller;
  7. using System.Text.RegularExpressions;
  8. using System.Linq;
  9. using Newtonsoft.Json;
  10. using System.ComponentModel;
  11. namespace Shadowsocks.WPF.Models
  12. {
  13. [Serializable]
  14. public class Server
  15. {
  16. public const string DefaultMethod = "chacha20-ietf-poly1305";
  17. public const int DefaultPort = 8388;
  18. #region ParseLegacyURL
  19. private static readonly Regex UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase);
  20. private static readonly Regex DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase);
  21. #endregion ParseLegacyURL
  22. private const int DefaultServerTimeoutSec = 5;
  23. public const int MaxServerTimeoutSec = 20;
  24. public string server;
  25. public int server_port;
  26. public string password;
  27. public string method;
  28. // optional fields
  29. [DefaultValue("")]
  30. [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
  31. public string plugin;
  32. [DefaultValue("")]
  33. [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
  34. public string plugin_opts;
  35. [DefaultValue("")]
  36. [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
  37. public string plugin_args;
  38. [DefaultValue("")]
  39. [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
  40. public string remarks;
  41. [DefaultValue("")]
  42. [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
  43. public string group;
  44. public int timeout;
  45. public override int GetHashCode()
  46. {
  47. return server.GetHashCode() ^ server_port;
  48. }
  49. public override bool Equals(object obj) => obj is Server o2 && server == o2.server && server_port == o2.server_port;
  50. public override string ToString()
  51. {
  52. if (string.IsNullOrEmpty(server))
  53. {
  54. return I18N.GetString("New server");
  55. }
  56. string serverStr = $"{FormalHostName}:{server_port}";
  57. return string.IsNullOrEmpty(remarks)
  58. ? serverStr
  59. : $"{remarks} ({serverStr})";
  60. }
  61. public string GetURL(bool legacyUrl = false)
  62. {
  63. if (legacyUrl && string.IsNullOrWhiteSpace(plugin))
  64. {
  65. // For backwards compatiblity, if no plugin, use old url format
  66. string p = $"{method}:{password}@{server}:{server_port}";
  67. string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(p));
  68. return string.IsNullOrEmpty(remarks)
  69. ? $"ss://{base64}"
  70. : $"ss://{base64}#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}";
  71. }
  72. UriBuilder u = new UriBuilder("ss", null);
  73. string b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{method}:{password}"));
  74. u.UserName = b64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
  75. u.Host = server;
  76. u.Port = server_port;
  77. u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8);
  78. if (!string.IsNullOrWhiteSpace(plugin))
  79. {
  80. NameValueCollection param = HttpUtility.ParseQueryString("");
  81. string pluginPart = plugin;
  82. if (!string.IsNullOrWhiteSpace(plugin_opts))
  83. {
  84. pluginPart += ";" + plugin_opts;
  85. }
  86. param["plugin"] = pluginPart;
  87. u.Query = param.ToString();
  88. }
  89. return u.ToString();
  90. }
  91. [JsonIgnore]
  92. public string FormalHostName
  93. {
  94. get
  95. {
  96. // CheckHostName() won't do a real DNS lookup
  97. return (Uri.CheckHostName(server)) switch
  98. {
  99. // Add square bracket when IPv6 (RFC3986)
  100. UriHostNameType.IPv6 => $"[{server}]",
  101. // IPv4 or domain name
  102. _ => server,
  103. };
  104. }
  105. }
  106. public Server()
  107. {
  108. server = "";
  109. server_port = DefaultPort;
  110. method = DefaultMethod;
  111. plugin = "";
  112. plugin_opts = "";
  113. plugin_args = "";
  114. password = "";
  115. remarks = "";
  116. timeout = DefaultServerTimeoutSec;
  117. }
  118. private static Server ParseLegacyURL(string ssURL)
  119. {
  120. var match = UrlFinder.Match(ssURL);
  121. if (!match.Success)
  122. return null;
  123. Server server = new Server();
  124. var base64 = match.Groups["base64"].Value.TrimEnd('/');
  125. var tag = match.Groups["tag"].Value;
  126. if (!string.IsNullOrEmpty(tag))
  127. {
  128. server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8);
  129. }
  130. Match details;
  131. try
  132. {
  133. details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String(
  134. base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))));
  135. }
  136. catch (FormatException)
  137. {
  138. return null;
  139. }
  140. if (!details.Success)
  141. return null;
  142. server.method = details.Groups["method"].Value;
  143. server.password = details.Groups["password"].Value;
  144. server.server = details.Groups["hostname"].Value;
  145. server.server_port = int.Parse(details.Groups["port"].Value);
  146. return server;
  147. }
  148. public static Server ParseURL(string serverUrl)
  149. {
  150. string _serverUrl = serverUrl.Trim();
  151. if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase))
  152. {
  153. return null;
  154. }
  155. Server legacyServer = ParseLegacyURL(serverUrl);
  156. if (legacyServer != null) //legacy
  157. {
  158. return legacyServer;
  159. }
  160. else //SIP002
  161. {
  162. Uri parsedUrl;
  163. try
  164. {
  165. parsedUrl = new Uri(serverUrl);
  166. }
  167. catch (UriFormatException)
  168. {
  169. return null;
  170. }
  171. Server server = new Server
  172. {
  173. remarks = HttpUtility.UrlDecode(parsedUrl.GetComponents(
  174. UriComponents.Fragment, UriFormat.Unescaped), Encoding.UTF8),
  175. server = parsedUrl.IdnHost,
  176. server_port = parsedUrl.Port,
  177. };
  178. // parse base64 UserInfo
  179. string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
  180. string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64
  181. string userInfo;
  182. try
  183. {
  184. userInfo = Encoding.UTF8.GetString(Convert.FromBase64String(
  185. base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')));
  186. }
  187. catch (FormatException)
  188. {
  189. return null;
  190. }
  191. string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2);
  192. if (userInfoParts.Length != 2)
  193. {
  194. return null;
  195. }
  196. server.method = userInfoParts[0];
  197. server.password = userInfoParts[1];
  198. NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query);
  199. string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2);
  200. if (pluginParts.Length > 0)
  201. {
  202. server.plugin = pluginParts[0] ?? "";
  203. }
  204. if (pluginParts.Length > 1)
  205. {
  206. server.plugin_opts = pluginParts[1] ?? "";
  207. }
  208. return server;
  209. }
  210. }
  211. public static List<Server> GetServers(string ssURL)
  212. {
  213. return ssURL
  214. .Split('\r', '\n', ' ')
  215. .Select(u => ParseURL(u))
  216. .Where(s => s != null)
  217. .ToList();
  218. }
  219. public string Identifier()
  220. {
  221. return server + ':' + server_port;
  222. }
  223. }
  224. }