From 7030c380a1f7c98975349b81b6c75fc2c4630cb9 Mon Sep 17 00:00:00 2001 From: celeron533 Date: Sun, 1 Sep 2019 21:36:49 +0800 Subject: [PATCH] Refine PAC server - Split the file operations to new PACDaemon - More precise matching logic for handling PAC content http request --- .../Controller/Service/GfwListUpdater.cs | 14 +- .../Controller/Service/PACDaemon.cs | 135 +++++++++++++ .../Controller/Service/PACServer.cs | 182 ++++++------------ .../Controller/ShadowsocksController.cs | 24 ++- shadowsocks-csharp/shadowsocks-csharp.csproj | 1 + 5 files changed, 215 insertions(+), 141 deletions(-) create mode 100644 shadowsocks-csharp/Controller/Service/PACDaemon.cs diff --git a/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs b/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs index 95b379a9..bc14b39b 100644 --- a/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs +++ b/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs @@ -48,24 +48,24 @@ namespace Shadowsocks.Controller public static bool MergeAndWritePACFile(string gfwListResult) { string abpContent = MergePACFile(gfwListResult); - if (File.Exists(PACServer.PAC_FILE)) + if (File.Exists(PACDaemon.PAC_FILE)) { - string original = FileManager.NonExclusiveReadAllText(PACServer.PAC_FILE, Encoding.UTF8); + string original = FileManager.NonExclusiveReadAllText(PACDaemon.PAC_FILE, Encoding.UTF8); if (original == abpContent) { return false; } } - File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); + File.WriteAllText(PACDaemon.PAC_FILE, abpContent, Encoding.UTF8); return true; } private static string MergePACFile(string gfwListResult) { string abpContent; - if (File.Exists(PACServer.USER_ABP_FILE)) + if (File.Exists(PACDaemon.USER_ABP_FILE)) { - abpContent = FileManager.NonExclusiveReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8); + abpContent = FileManager.NonExclusiveReadAllText(PACDaemon.USER_ABP_FILE, Encoding.UTF8); } else { @@ -73,9 +73,9 @@ namespace Shadowsocks.Controller } List userruleLines = new List(); - if (File.Exists(PACServer.USER_RULE_FILE)) + if (File.Exists(PACDaemon.USER_RULE_FILE)) { - string userrulesString = FileManager.NonExclusiveReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8); + string userrulesString = FileManager.NonExclusiveReadAllText(PACDaemon.USER_RULE_FILE, Encoding.UTF8); userruleLines = ParseToValidList(userrulesString); } diff --git a/shadowsocks-csharp/Controller/Service/PACDaemon.cs b/shadowsocks-csharp/Controller/Service/PACDaemon.cs new file mode 100644 index 00000000..76be30aa --- /dev/null +++ b/shadowsocks-csharp/Controller/Service/PACDaemon.cs @@ -0,0 +1,135 @@ +using Shadowsocks.Properties; +using Shadowsocks.Util; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.Controller +{ + + /// + /// Processing the PAC file content + /// + public class PACDaemon + { + public const string PAC_FILE = "pac.txt"; + public const string USER_RULE_FILE = "user-rule.txt"; + public const string USER_ABP_FILE = "abp.txt"; + + FileSystemWatcher PACFileWatcher; + FileSystemWatcher UserRuleFileWatcher; + + public event EventHandler PACFileChanged; + public event EventHandler UserRuleFileChanged; + + public PACDaemon() + { + this.WatchPacFile(); + this.WatchUserRuleFile(); + } + + + public string TouchPACFile() + { + if (File.Exists(PAC_FILE)) + { + return PAC_FILE; + } + else + { + FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt); + return PAC_FILE; + } + } + + internal string TouchUserRuleFile() + { + if (File.Exists(USER_RULE_FILE)) + { + return USER_RULE_FILE; + } + else + { + File.WriteAllText(USER_RULE_FILE, Resources.user_rule); + return USER_RULE_FILE; + } + } + + internal string GetPACContent() + { + if (File.Exists(PAC_FILE)) + { + return File.ReadAllText(PAC_FILE, Encoding.UTF8); + } + else + { + return Utils.UnGzip(Resources.proxy_pac_txt); + } + } + + + private void WatchPacFile() + { + PACFileWatcher?.Dispose(); + PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); + PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + PACFileWatcher.Filter = PAC_FILE; + PACFileWatcher.Changed += PACFileWatcher_Changed; + PACFileWatcher.Created += PACFileWatcher_Changed; + PACFileWatcher.Deleted += PACFileWatcher_Changed; + PACFileWatcher.Renamed += PACFileWatcher_Changed; + PACFileWatcher.EnableRaisingEvents = true; + } + + private void WatchUserRuleFile() + { + UserRuleFileWatcher?.Dispose(); + UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); + UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + UserRuleFileWatcher.Filter = USER_RULE_FILE; + UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.EnableRaisingEvents = true; + } + + #region FileSystemWatcher.OnChanged() + // FileSystemWatcher Changed event is raised twice + // http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice + // Add a short delay to avoid raise event twice in a short period + private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) + { + if (PACFileChanged != null) + { + Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); + Task.Factory.StartNew(() => + { + ((FileSystemWatcher)sender).EnableRaisingEvents = false; + System.Threading.Thread.Sleep(10); + PACFileChanged(this, new EventArgs()); + ((FileSystemWatcher)sender).EnableRaisingEvents = true; + }); + } + } + + private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) + { + if (UserRuleFileChanged != null) + { + Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); + Task.Factory.StartNew(() => + { + ((FileSystemWatcher)sender).EnableRaisingEvents = false; + System.Threading.Thread.Sleep(10); + UserRuleFileChanged(this, new EventArgs()); + ((FileSystemWatcher)sender).EnableRaisingEvents = true; + }); + } + } + #endregion + } +} diff --git a/shadowsocks-csharp/Controller/Service/PACServer.cs b/shadowsocks-csharp/Controller/Service/PACServer.cs index f1ee3218..e1f11a8f 100644 --- a/shadowsocks-csharp/Controller/Service/PACServer.cs +++ b/shadowsocks-csharp/Controller/Service/PACServer.cs @@ -15,28 +15,21 @@ namespace Shadowsocks.Controller { public class PACServer : Listener.Service { - public const string PAC_FILE = "pac.txt"; - public const string USER_RULE_FILE = "user-rule.txt"; - public const string USER_ABP_FILE = "abp.txt"; + public const string RESOURCE_NAME = "pac"; private string PacSecret { get; set; } = ""; public string PacUrl { get; private set; } = ""; - FileSystemWatcher PACFileWatcher; - FileSystemWatcher UserRuleFileWatcher; private Configuration _config; + private PACDaemon _pacDaemon; - public event EventHandler PACFileChanged; - public event EventHandler UserRuleFileChanged; - - public PACServer() + public PACServer(PACDaemon pacDaemon) { - this.WatchPacFile(); - this.WatchUserRuleFile(); + _pacDaemon = pacDaemon; } - public void UpdateConfiguration(Configuration config) + public void UpdatePACURL(Configuration config) { this._config = config; @@ -51,7 +44,7 @@ namespace Shadowsocks.Controller PacSecret = ""; } - PacUrl = $"http://{config.localHost}:{config.localPort}/pac?t={GetTimestamp(DateTime.Now)}{PacSecret}"; + PacUrl = $"http://{config.localHost}:{config.localPort}/{RESOURCE_NAME}?t={GetTimestamp(DateTime.Now)}{PacSecret}"; } @@ -66,15 +59,61 @@ namespace Shadowsocks.Controller { return false; } + try { + /* + * RFC 7230 + * + GET /hello.txt HTTP/1.1 + User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 + Host: www.example.com + Accept-Language: en, mi + */ + string request = Encoding.UTF8.GetString(firstPacket, 0, length); string[] lines = request.Split('\r', '\n'); bool hostMatch = false, pathMatch = false, useSocks = false; bool secretMatch = PacSecret.IsNullOrEmpty(); - foreach (string line in lines) + + if (lines.Length < 2) // need at lease RequestLine + Host { - string[] kv = line.Split(new char[] { ':' }, 2); + return false; + } + + // parse request line + string requestLine = lines[0]; + // GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1 + string[] requestItems = requestLine.Split(' '); + if (requestItems.Length == 3 && requestItems[0] == "GET") + { + int index = requestItems[1].IndexOf('?'); + if (index < 0) + { + index = requestItems[1].Length; + } + string resourceString = requestItems[1].Substring(0, index).Remove(0, 1); + if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase)) + { + pathMatch = true; + if (!secretMatch) + { + string queryString = requestItems[1].Substring(index); + if (queryString.Contains(PacSecret)) + { + secretMatch = true; + } + } + } + } + + // parse request header + for (int i = 1; i < lines.Length; i++) + { + if (string.IsNullOrEmpty(lines[i])) + continue; + + string[] kv = lines[i].Split(new char[] { ':' }, 2); if (kv.Length == 2) { if (kv[0] == "Host") @@ -93,21 +132,8 @@ namespace Shadowsocks.Controller // } //} } - else if (kv.Length == 1) - { - if (line.IndexOf("pac", StringComparison.Ordinal) >= 0) - { - pathMatch = true; - } - if (!secretMatch) - { - if (line.IndexOf(PacSecret, StringComparison.Ordinal) >= 0) - { - secretMatch = true; - } - } - } } + if (hostMatch && pathMatch) { if (!secretMatch) @@ -128,43 +154,7 @@ namespace Shadowsocks.Controller } } - public string TouchPACFile() - { - if (File.Exists(PAC_FILE)) - { - return PAC_FILE; - } - else - { - FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt); - return PAC_FILE; - } - } - - internal string TouchUserRuleFile() - { - if (File.Exists(USER_RULE_FILE)) - { - return USER_RULE_FILE; - } - else - { - File.WriteAllText(USER_RULE_FILE, Resources.user_rule); - return USER_RULE_FILE; - } - } - private string GetPACContent() - { - if (File.Exists(PAC_FILE)) - { - return File.ReadAllText(PAC_FILE, Encoding.UTF8); - } - else - { - return Utils.UnGzip(Resources.proxy_pac_txt); - } - } public void SendResponse(Socket socket, bool useSocks) { @@ -174,7 +164,7 @@ namespace Shadowsocks.Controller string proxy = GetPACAddress(localEndPoint, useSocks); - string pacContent = GetPACContent().Replace("__PROXY__", proxy); + string pacContent = _pacDaemon.GetPACContent().Replace("__PROXY__", proxy); string responseHead = String.Format(@"HTTP/1.1 200 OK Server: Shadowsocks @@ -205,66 +195,6 @@ Connection: Close { } } - private void WatchPacFile() - { - PACFileWatcher?.Dispose(); - PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); - PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; - PACFileWatcher.Filter = PAC_FILE; - PACFileWatcher.Changed += PACFileWatcher_Changed; - PACFileWatcher.Created += PACFileWatcher_Changed; - PACFileWatcher.Deleted += PACFileWatcher_Changed; - PACFileWatcher.Renamed += PACFileWatcher_Changed; - PACFileWatcher.EnableRaisingEvents = true; - } - - private void WatchUserRuleFile() - { - UserRuleFileWatcher?.Dispose(); - UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); - UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; - UserRuleFileWatcher.Filter = USER_RULE_FILE; - UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; - UserRuleFileWatcher.EnableRaisingEvents = true; - } - - #region FileSystemWatcher.OnChanged() - // FileSystemWatcher Changed event is raised twice - // http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice - // Add a short delay to avoid raise event twice in a short period - private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) - { - if (PACFileChanged != null) - { - Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); - Task.Factory.StartNew(() => - { - ((FileSystemWatcher)sender).EnableRaisingEvents = false; - System.Threading.Thread.Sleep(10); - PACFileChanged(this, new EventArgs()); - ((FileSystemWatcher)sender).EnableRaisingEvents = true; - }); - } - } - - private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) - { - if (UserRuleFileChanged != null) - { - Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); - Task.Factory.StartNew(() => - { - ((FileSystemWatcher)sender).EnableRaisingEvents = false; - System.Threading.Thread.Sleep(10); - UserRuleFileChanged(this, new EventArgs()); - ((FileSystemWatcher)sender).EnableRaisingEvents = true; - }); - } - } - #endregion private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) { diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 70d5eb2f..0056e20b 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -28,6 +28,7 @@ namespace Shadowsocks.Controller private Thread _trafficThread; private Listener _listener; + private PACDaemon _pacDaemon; private PACServer _pacServer; private Configuration _config; private StrategyManager _strategyManager; @@ -299,14 +300,14 @@ namespace Shadowsocks.Controller public void TouchPACFile() { - string pacFilename = _pacServer.TouchPACFile(); + string pacFilename = _pacDaemon.TouchPACFile(); PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename }); } public void TouchUserRuleFile() { - string userRuleFilename = _pacServer.TouchUserRuleFile(); + string userRuleFilename = _pacDaemon.TouchUserRuleFile(); UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename }); } @@ -468,13 +469,20 @@ namespace Shadowsocks.Controller { privoxyRunner = new PrivoxyRunner(); } + + if (_pacDaemon == null) + { + _pacDaemon = new PACDaemon(); + _pacDaemon.PACFileChanged += PacDaemon_PACFileChanged; + _pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged; + } + if (_pacServer == null) { - _pacServer = new PACServer(); - _pacServer.PACFileChanged += PacServer_PACFileChanged; - _pacServer.UserRuleFileChanged += PacServer_UserRuleFileChanged; + _pacServer = new PACServer(_pacDaemon); } - _pacServer.UpdateConfiguration(_config); + + _pacServer.UpdatePACURL(_config); if (gfwListUpdater == null) { gfwListUpdater = new GFWListUpdater(); @@ -561,7 +569,7 @@ namespace Shadowsocks.Controller SystemProxy.Update(_config, false, _pacServer); } - private void PacServer_PACFileChanged(object sender, EventArgs e) + private void PacDaemon_PACFileChanged(object sender, EventArgs e) { UpdateSystemProxy(); } @@ -577,7 +585,7 @@ namespace Shadowsocks.Controller } private static readonly IEnumerable IgnoredLineBegins = new[] { '!', '[' }; - private void PacServer_UserRuleFileChanged(object sender, EventArgs e) + private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e) { if (!File.Exists(Utils.GetTempPath("gfwlist.txt"))) { diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 40e59bf3..a37293c3 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -103,6 +103,7 @@ +