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.

Configuration.cs 14 kB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Windows;
  7. using Newtonsoft.Json;
  8. using NLog;
  9. using Shadowsocks.Controller;
  10. namespace Shadowsocks.Model
  11. {
  12. [Serializable]
  13. public class Configuration
  14. {
  15. [JsonIgnore]
  16. private static readonly Logger logger = LogManager.GetCurrentClassLogger();
  17. public string version;
  18. public List<Server> configs;
  19. public List<string> onlineConfigSource;
  20. // when strategy is set, index is ignored
  21. public string strategy;
  22. public int index;
  23. public bool global;
  24. public bool enabled;
  25. public bool shareOverLan;
  26. public bool firstRun;
  27. public int localPort;
  28. public bool portableMode;
  29. public bool showPluginOutput;
  30. public string pacUrl;
  31. public bool useOnlinePac;
  32. public bool secureLocalPac; // enable secret for PAC server
  33. public bool regeneratePacOnUpdate; // regenerate pac.txt on version update
  34. public bool autoCheckUpdate;
  35. public bool checkPreRelease;
  36. public string skippedUpdateVersion; // skip the update with this version number
  37. public bool isVerboseLogging;
  38. // hidden options
  39. public bool isIPv6Enabled; // for experimental ipv6 support
  40. public bool generateLegacyUrl; // for pre-sip002 url compatibility
  41. public string geositeUrl; // for custom geosite source (and rule group)
  42. public List<string> geositeDirectGroups; // groups of domains that we connect without the proxy
  43. public List<string> geositeProxiedGroups; // groups of domains that we connect via the proxy
  44. public bool geositePreferDirect; // a.k.a blacklist mode
  45. public string userAgent;
  46. //public NLogConfig.LogLevel logLevel;
  47. public LogViewerConfig logViewer;
  48. public ForwardProxyConfig proxy;
  49. public HotkeyConfig hotkey;
  50. [JsonIgnore]
  51. public bool firstRunOnNewVersion;
  52. public Configuration()
  53. {
  54. version = UpdateChecker.Version;
  55. strategy = "";
  56. index = 0;
  57. global = false;
  58. enabled = false;
  59. shareOverLan = false;
  60. firstRun = true;
  61. localPort = 1080;
  62. portableMode = true;
  63. showPluginOutput = false;
  64. pacUrl = "";
  65. useOnlinePac = false;
  66. secureLocalPac = true;
  67. regeneratePacOnUpdate = true;
  68. autoCheckUpdate = false;
  69. checkPreRelease = false;
  70. skippedUpdateVersion = "";
  71. isVerboseLogging = false;
  72. // hidden options
  73. isIPv6Enabled = false;
  74. generateLegacyUrl = false;
  75. geositeUrl = "";
  76. geositeDirectGroups = new List<string>()
  77. {
  78. "private",
  79. "cn",
  80. "geolocation-!cn@cn",
  81. };
  82. geositeProxiedGroups = new List<string>()
  83. {
  84. "geolocation-!cn",
  85. };
  86. geositePreferDirect = false;
  87. userAgent = "ShadowsocksWindows/$version";
  88. logViewer = new LogViewerConfig();
  89. proxy = new ForwardProxyConfig();
  90. hotkey = new HotkeyConfig();
  91. firstRunOnNewVersion = false;
  92. configs = new List<Server>();
  93. onlineConfigSource = new List<string>();
  94. }
  95. [JsonIgnore]
  96. public string userAgentString; // $version substituted with numeral version in it
  97. [JsonIgnore]
  98. NLogConfig nLogConfig;
  99. private static readonly string CONFIG_FILE = "gui-config.json";
  100. #if DEBUG
  101. private static readonly NLogConfig.LogLevel verboseLogLevel = NLogConfig.LogLevel.Trace;
  102. #else
  103. private static readonly NLogConfig.LogLevel verboseLogLevel = NLogConfig.LogLevel.Debug;
  104. #endif
  105. [JsonIgnore]
  106. public string LocalHost => isIPv6Enabled ? "[::1]" : "127.0.0.1";
  107. public Server GetCurrentServer()
  108. {
  109. if (index >= 0 && index < configs.Count)
  110. return configs[index];
  111. else
  112. return GetDefaultServer();
  113. }
  114. public WebProxy WebProxy => enabled
  115. ? new WebProxy(
  116. isIPv6Enabled
  117. ? $"[{IPAddress.IPv6Loopback}]"
  118. : IPAddress.Loopback.ToString(),
  119. localPort)
  120. : null;
  121. /// <summary>
  122. /// Used by multiple forms to validate a server.
  123. /// Communication is done by throwing exceptions.
  124. /// </summary>
  125. /// <param name="server"></param>
  126. public static void CheckServer(Server server)
  127. {
  128. CheckServer(server.server);
  129. CheckPort(server.server_port);
  130. CheckPassword(server.password);
  131. CheckTimeout(server.timeout, Server.MaxServerTimeoutSec);
  132. }
  133. /// <summary>
  134. /// Loads the configuration from file.
  135. /// </summary>
  136. /// <returns>An Configuration object.</returns>
  137. public static Configuration Load()
  138. {
  139. Configuration config;
  140. if (File.Exists(CONFIG_FILE))
  141. {
  142. try
  143. {
  144. string configContent = File.ReadAllText(CONFIG_FILE);
  145. config = JsonConvert.DeserializeObject<Configuration>(configContent, new JsonSerializerSettings()
  146. {
  147. ObjectCreationHandling = ObjectCreationHandling.Replace
  148. });
  149. return config;
  150. }
  151. catch (Exception e)
  152. {
  153. if (!(e is FileNotFoundException))
  154. logger.LogUsefulException(e);
  155. }
  156. }
  157. config = new Configuration();
  158. return config;
  159. }
  160. /// <summary>
  161. /// Process the loaded configurations and set up things.
  162. /// </summary>
  163. /// <param name="config">A reference of Configuration object.</param>
  164. public static void Process(ref Configuration config)
  165. {
  166. // Verify if the configured geosite groups exist.
  167. // Reset to default if ANY one of the configured group doesn't exist.
  168. if (!ValidateGeositeGroupList(config.geositeDirectGroups))
  169. ResetGeositeDirectGroup(ref config.geositeDirectGroups);
  170. if (!ValidateGeositeGroupList(config.geositeProxiedGroups))
  171. ResetGeositeProxiedGroup(ref config.geositeProxiedGroups);
  172. // Mark the first run of a new version.
  173. var appVersion = new Version(UpdateChecker.Version);
  174. var configVersion = new Version(config.version);
  175. if (appVersion.CompareTo(configVersion) > 0)
  176. {
  177. config.firstRunOnNewVersion = true;
  178. }
  179. // Add an empty server configuration
  180. if (config.configs.Count == 0)
  181. config.configs.Add(GetDefaultServer());
  182. // Selected server
  183. if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
  184. config.index = 0;
  185. if (config.index >= config.configs.Count)
  186. config.index = config.configs.Count - 1;
  187. // Check OS IPv6 support
  188. if (!System.Net.Sockets.Socket.OSSupportsIPv6)
  189. config.isIPv6Enabled = false;
  190. config.proxy.CheckConfig();
  191. // Replace $version with the version number.
  192. config.userAgentString = config.userAgent.Replace("$version", config.version);
  193. // NLog log level
  194. try
  195. {
  196. config.nLogConfig = NLogConfig.LoadXML();
  197. switch (config.nLogConfig.GetLogLevel())
  198. {
  199. case NLogConfig.LogLevel.Fatal:
  200. case NLogConfig.LogLevel.Error:
  201. case NLogConfig.LogLevel.Warn:
  202. case NLogConfig.LogLevel.Info:
  203. config.isVerboseLogging = false;
  204. break;
  205. case NLogConfig.LogLevel.Debug:
  206. case NLogConfig.LogLevel.Trace:
  207. config.isVerboseLogging = true;
  208. break;
  209. }
  210. }
  211. catch (Exception e)
  212. {
  213. MessageBox.Show($"Cannot get the log level from NLog config file. Please check if the nlog config file exists with corresponding XML nodes.\n{e.Message}");
  214. }
  215. }
  216. /// <summary>
  217. /// Saves the Configuration object to file.
  218. /// </summary>
  219. /// <param name="config">A Configuration object.</param>
  220. public static void Save(Configuration config)
  221. {
  222. config.configs = SortByOnlineConfig(config.configs);
  223. FileStream configFileStream = null;
  224. StreamWriter configStreamWriter = null;
  225. try
  226. {
  227. configFileStream = File.Open(CONFIG_FILE, FileMode.Create);
  228. configStreamWriter = new StreamWriter(configFileStream);
  229. var jsonString = JsonConvert.SerializeObject(config, Formatting.Indented);
  230. configStreamWriter.Write(jsonString);
  231. configStreamWriter.Flush();
  232. // NLog
  233. config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
  234. NLogConfig.SaveXML(config.nLogConfig);
  235. }
  236. catch (Exception e)
  237. {
  238. logger.LogUsefulException(e);
  239. }
  240. finally
  241. {
  242. if (configStreamWriter != null)
  243. configStreamWriter.Dispose();
  244. if (configFileStream != null)
  245. configFileStream.Dispose();
  246. }
  247. }
  248. public static List<Server> SortByOnlineConfig(IEnumerable<Server> servers)
  249. {
  250. var groups = servers.GroupBy(s => s.group);
  251. List<Server> ret = new List<Server>();
  252. ret.AddRange(groups.Where(g => string.IsNullOrEmpty(g.Key)).SelectMany(g => g));
  253. ret.AddRange(groups.Where(g => !string.IsNullOrEmpty(g.Key)).SelectMany(g => g));
  254. return ret;
  255. }
  256. /// <summary>
  257. /// Validates if the groups in the list are all valid.
  258. /// </summary>
  259. /// <param name="groups">The list of groups to validate.</param>
  260. /// <returns>
  261. /// True if all groups are valid.
  262. /// False if any one of them is invalid.
  263. /// </returns>
  264. public static bool ValidateGeositeGroupList(List<string> groups)
  265. {
  266. foreach (var geositeGroup in groups)
  267. if (!GeositeUpdater.CheckGeositeGroup(geositeGroup)) // found invalid group
  268. {
  269. #if DEBUG
  270. logger.Debug($"Available groups:");
  271. foreach (var group in GeositeUpdater.Geosites.Keys)
  272. logger.Debug($"{group}");
  273. #endif
  274. logger.Warn($"The Geosite group {geositeGroup} doesn't exist. Resetting to default groups.");
  275. return false;
  276. }
  277. return true;
  278. }
  279. public static void ResetGeositeDirectGroup(ref List<string> geositeDirectGroups)
  280. {
  281. geositeDirectGroups.Clear();
  282. geositeDirectGroups.Add("private");
  283. geositeDirectGroups.Add("cn");
  284. geositeDirectGroups.Add("geolocation-!cn@cn");
  285. }
  286. public static void ResetGeositeProxiedGroup(ref List<string> geositeProxiedGroups)
  287. {
  288. geositeProxiedGroups.Clear();
  289. geositeProxiedGroups.Add("geolocation-!cn");
  290. }
  291. public static void ResetUserAgent(Configuration config)
  292. {
  293. config.userAgent = "ShadowsocksWindows/$version";
  294. config.userAgentString = config.userAgent.Replace("$version", config.version);
  295. }
  296. public static Server AddDefaultServerOrServer(Configuration config, Server server = null, int? index = null)
  297. {
  298. if (config?.configs != null)
  299. {
  300. server = (server ?? GetDefaultServer());
  301. config.configs.Insert(index.GetValueOrDefault(config.configs.Count), server);
  302. //if (index.HasValue)
  303. // config.configs.Insert(index.Value, server);
  304. //else
  305. // config.configs.Add(server);
  306. }
  307. return server;
  308. }
  309. public static Server GetDefaultServer()
  310. {
  311. return new Server();
  312. }
  313. public static void CheckPort(int port)
  314. {
  315. if (port <= 0 || port > 65535)
  316. throw new ArgumentException(I18N.GetString("Port out of range"));
  317. }
  318. public static void CheckLocalPort(int port)
  319. {
  320. CheckPort(port);
  321. if (port == 8123)
  322. throw new ArgumentException(I18N.GetString("Port can't be 8123"));
  323. }
  324. private static void CheckPassword(string password)
  325. {
  326. if (string.IsNullOrEmpty(password))
  327. throw new ArgumentException(I18N.GetString("Password can not be blank"));
  328. }
  329. public static void CheckServer(string server)
  330. {
  331. if (string.IsNullOrEmpty(server))
  332. throw new ArgumentException(I18N.GetString("Server IP can not be blank"));
  333. }
  334. public static void CheckTimeout(int timeout, int maxTimeout)
  335. {
  336. if (timeout <= 0 || timeout > maxTimeout)
  337. throw new ArgumentException(
  338. I18N.GetString("Timeout is invalid, it should not exceed {0}", maxTimeout));
  339. }
  340. }
  341. }