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 5.9 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Text.Json.Serialization;
  5. namespace Shadowsocks.Models
  6. {
  7. public class Server : IServer
  8. {
  9. /// <inheritdoc/>
  10. [JsonPropertyName("server")]
  11. public string Host { get; set; }
  12. /// <inheritdoc/>
  13. [JsonPropertyName("server_port")]
  14. public int Port { get; set; }
  15. /// <inheritdoc/>
  16. public string Password { get; set; }
  17. /// <inheritdoc/>
  18. public string Method { get; set; }
  19. /// <inheritdoc/>
  20. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
  21. public string? Plugin { get; set; }
  22. /// <inheritdoc/>
  23. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
  24. public string? PluginOpts { get; set; }
  25. /// <summary>
  26. /// Gets or sets the arguments passed to the plugin process.
  27. /// </summary>
  28. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
  29. public List<string>? PluginArgs { get; set; }
  30. /// <inheritdoc/>
  31. [JsonPropertyName("remarks")]
  32. public string Name { get; set; }
  33. /// <inheritdoc/>
  34. [JsonPropertyName("id")]
  35. public string Uuid { get; set; }
  36. public Server()
  37. {
  38. Host = "";
  39. Port = 8388;
  40. Password = "";
  41. Method = "chacha20-ietf-poly1305";
  42. Name = "";
  43. Uuid = Guid.NewGuid().ToString();
  44. }
  45. public bool Equals(IServer? other) => other is Server anotherServer && Uuid == anotherServer.Uuid;
  46. public override int GetHashCode() => Uuid.GetHashCode();
  47. public override string ToString() => Name;
  48. /// <summary>
  49. /// Converts this server object into an ss:// URL.
  50. /// </summary>
  51. /// <returns></returns>
  52. public Uri ToUrl()
  53. {
  54. UriBuilder uriBuilder = new("ss", Host, Port)
  55. {
  56. UserName = Utilities.Base64Url.Encode($"{Method}:{Password}"),
  57. Fragment = Name,
  58. };
  59. if (!string.IsNullOrEmpty(Plugin))
  60. if (!string.IsNullOrEmpty(PluginOpts))
  61. uriBuilder.Query = $"plugin={Uri.EscapeDataString($"{Plugin};{PluginOpts}")}"; // manually escape as a workaround
  62. else
  63. uriBuilder.Query = $"plugin={Plugin}";
  64. return uriBuilder.Uri;
  65. }
  66. /// <summary>
  67. /// Tries to parse an ss:// URL into a Server object.
  68. /// </summary>
  69. /// <param name="url">The ss:// URL to parse.</param>
  70. /// <param name="server">
  71. /// A Server object represented by the URL.
  72. /// A new empty Server object if the URL is invalid.
  73. /// </param>
  74. /// <returns>True for success. False for failure.</returns>
  75. public static bool TryParse(string url, [NotNullWhen(true)] out Server? server)
  76. {
  77. server = null;
  78. return Uri.TryCreate(url, UriKind.Absolute, out var uri) && TryParse(uri, out server);
  79. }
  80. /// <summary>
  81. /// Tries to parse an ss:// URL into a Server object.
  82. /// </summary>
  83. /// <param name="uri">The ss:// URL to parse.</param>
  84. /// <param name="server">
  85. /// A Server object represented by the URL.
  86. /// A new empty Server object if the URL is invalid.
  87. /// </param>
  88. /// <returns>True for success. False for failure.</returns>
  89. public static bool TryParse(Uri uri, [NotNullWhen(true)] out Server? server)
  90. {
  91. server = null;
  92. try
  93. {
  94. if (uri.Scheme != "ss")
  95. return false;
  96. var userinfo_base64url = uri.UserInfo;
  97. var userinfo = Utilities.Base64Url.DecodeToString(userinfo_base64url);
  98. var userinfoSplitArray = userinfo.Split(':', 2);
  99. var method = userinfoSplitArray[0];
  100. var password = userinfoSplitArray[1];
  101. var host = uri.HostNameType == UriHostNameType.IPv6 ? uri.Host[1..^1] : uri.Host;
  102. var escapedFragment = string.IsNullOrEmpty(uri.Fragment) ? uri.Fragment : uri.Fragment[1..];
  103. var name = Uri.UnescapeDataString(escapedFragment);
  104. server = new Server()
  105. {
  106. Name = name,
  107. Uuid = Guid.NewGuid().ToString(),
  108. Host = host,
  109. Port = uri.Port,
  110. Password = password,
  111. Method = method,
  112. };
  113. // find the plugin query
  114. var parsedQueriesArray = uri.Query.Split('?', '&');
  115. var pluginQueryContent = "";
  116. foreach (var query in parsedQueriesArray)
  117. {
  118. if (query.StartsWith("plugin=") && query.Length > 7)
  119. {
  120. pluginQueryContent = query[7..]; // remove "plugin="
  121. }
  122. }
  123. if (string.IsNullOrEmpty(pluginQueryContent)) // no plugin
  124. return true;
  125. var unescapedpluginQuery = Uri.UnescapeDataString(pluginQueryContent);
  126. var parsedPluginQueryArray = unescapedpluginQuery.Split(';', 2);
  127. if (parsedPluginQueryArray.Length == 1)
  128. {
  129. server.Plugin = parsedPluginQueryArray[0];
  130. }
  131. else if (parsedPluginQueryArray.Length == 2) // is valid plugin query
  132. {
  133. server.Plugin = parsedPluginQueryArray[0];
  134. server.PluginOpts = parsedPluginQueryArray[1];
  135. }
  136. return true;
  137. }
  138. catch
  139. {
  140. return false;
  141. }
  142. }
  143. }
  144. }