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

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