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.6 kB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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.Model
  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. string tag = string.Empty;
  64. string url = string.Empty;
  65. if (legacyUrl && string.IsNullOrWhiteSpace(plugin))
  66. {
  67. // For backwards compatiblity, if no plugin, use old url format
  68. string parts = $"{method}:{password}@{server}:{server_port}";
  69. string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts));
  70. url = base64;
  71. }
  72. else
  73. {
  74. // SIP002
  75. string parts = $"{method}:{password}";
  76. string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts));
  77. string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
  78. url = string.Format(
  79. "{0}@{1}:{2}/",
  80. websafeBase64,
  81. FormalHostName,
  82. server_port
  83. );
  84. if (!string.IsNullOrWhiteSpace(plugin))
  85. {
  86. string pluginPart = plugin;
  87. if (!string.IsNullOrWhiteSpace(plugin_opts))
  88. {
  89. pluginPart += ";" + plugin_opts;
  90. }
  91. string pluginQuery = "?plugin=" + HttpUtility.UrlEncode(pluginPart, Encoding.UTF8);
  92. url += pluginQuery;
  93. }
  94. }
  95. if (!string.IsNullOrEmpty(remarks))
  96. {
  97. tag = $"#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}";
  98. }
  99. return $"ss://{url}{tag}";
  100. }
  101. [JsonIgnore]
  102. public string FormalHostName
  103. {
  104. get
  105. {
  106. // CheckHostName() won't do a real DNS lookup
  107. switch (Uri.CheckHostName(server))
  108. {
  109. case UriHostNameType.IPv6: // Add square bracket when IPv6 (RFC3986)
  110. return $"[{server}]";
  111. default: // IPv4 or domain name
  112. return server;
  113. }
  114. }
  115. }
  116. public Server()
  117. {
  118. server = "";
  119. server_port = DefaultPort;
  120. method = DefaultMethod;
  121. plugin = "";
  122. plugin_opts = "";
  123. plugin_args = "";
  124. password = "";
  125. remarks = "";
  126. timeout = DefaultServerTimeoutSec;
  127. }
  128. private static Server ParseLegacyURL(string ssURL)
  129. {
  130. var match = UrlFinder.Match(ssURL);
  131. if (!match.Success)
  132. return null;
  133. Server server = new Server();
  134. var base64 = match.Groups["base64"].Value.TrimEnd('/');
  135. var tag = match.Groups["tag"].Value;
  136. if (!string.IsNullOrEmpty(tag))
  137. {
  138. server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8);
  139. }
  140. Match details = null;
  141. try
  142. {
  143. details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String(
  144. base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))));
  145. }
  146. catch (FormatException)
  147. {
  148. return null;
  149. }
  150. if (!details.Success)
  151. return null;
  152. server.method = details.Groups["method"].Value;
  153. server.password = details.Groups["password"].Value;
  154. server.server = details.Groups["hostname"].Value;
  155. server.server_port = int.Parse(details.Groups["port"].Value);
  156. return server;
  157. }
  158. public static Server ParseURL(string serverUrl)
  159. {
  160. string _serverUrl = serverUrl.Trim();
  161. if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase))
  162. {
  163. return null;
  164. }
  165. Server legacyServer = ParseLegacyURL(serverUrl);
  166. if (legacyServer != null) //legacy
  167. {
  168. return legacyServer;
  169. }
  170. else //SIP002
  171. {
  172. Uri parsedUrl;
  173. try
  174. {
  175. parsedUrl = new Uri(serverUrl);
  176. }
  177. catch (UriFormatException)
  178. {
  179. return null;
  180. }
  181. Server server = new Server
  182. {
  183. remarks = HttpUtility.UrlDecode(parsedUrl.GetComponents(
  184. UriComponents.Fragment, UriFormat.Unescaped), Encoding.UTF8),
  185. server = parsedUrl.IdnHost,
  186. server_port = parsedUrl.Port,
  187. };
  188. // parse base64 UserInfo
  189. string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
  190. string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64
  191. string userInfo = "";
  192. try
  193. {
  194. userInfo = Encoding.UTF8.GetString(Convert.FromBase64String(
  195. base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')));
  196. }
  197. catch (FormatException)
  198. {
  199. return null;
  200. }
  201. string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2);
  202. if (userInfoParts.Length != 2)
  203. {
  204. return null;
  205. }
  206. server.method = userInfoParts[0];
  207. server.password = userInfoParts[1];
  208. NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query);
  209. string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2);
  210. if (pluginParts.Length > 0)
  211. {
  212. server.plugin = pluginParts[0] ?? "";
  213. }
  214. if (pluginParts.Length > 1)
  215. {
  216. server.plugin_opts = pluginParts[1] ?? "";
  217. }
  218. return server;
  219. }
  220. }
  221. public static List<Server> GetServers(string ssURL)
  222. {
  223. return ssURL
  224. .Split('\r', '\n', ' ')
  225. .Select(u => ParseURL(u))
  226. .Where(s => s != null)
  227. .ToList();
  228. }
  229. public string Identifier()
  230. {
  231. return server + ':' + server_port;
  232. }
  233. }
  234. }