diff --git a/shadowsocks-csharp/Controller/GfwListUpdater.cs b/shadowsocks-csharp/Controller/GfwListUpdater.cs new file mode 100644 index 00000000..2b3db383 --- /dev/null +++ b/shadowsocks-csharp/Controller/GfwListUpdater.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.IO; + +namespace Shadowsocks.Controller +{ + public class GfwListUpdater + { + private const string GFWLIST_URL = "https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt"; + + public IWebProxy proxy = null; + + public class GfwListDownloadCompletedArgs : EventArgs + { + public string Content; + } + + public event EventHandler DownloadCompleted; + + public event ErrorEventHandler Error; + + public void Download() + { + WebClient http = new WebClient(); + http.Proxy = proxy; + http.DownloadStringCompleted += http_DownloadStringCompleted; + http.DownloadStringAsync(new Uri(GFWLIST_URL)); + } + + protected void ReportError(Exception e) + { + if (Error != null) + { + Error(this, new ErrorEventArgs(e)); + } + } + + private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) + { + try + { + string response = e.Result; + if (DownloadCompleted != null) + { + DownloadCompleted(this, new GfwListDownloadCompletedArgs + { + Content = response + }); + } + } + catch (Exception ex) + { + ReportError(ex); + } + } + + public class Parser + { + private string _Content; + + public string Content + { + get { return _Content; } + } + + public Parser(string response) + { + byte[] bytes = Convert.FromBase64String(response); + this._Content = Encoding.ASCII.GetString(bytes); + } + + public string[] GetValidLines() + { + string[] lines = Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + List valid_lines = new List(lines.Length); + foreach (string line in lines) + { + if (line.StartsWith("!") || line.StartsWith("[")) + continue; + valid_lines.Add(line); + } + return valid_lines.ToArray(); + } + + } + + } +} diff --git a/shadowsocks-csharp/Controller/PACServer.cs b/shadowsocks-csharp/Controller/PACServer.cs index f02476ae..fbd1989b 100755 --- a/shadowsocks-csharp/Controller/PACServer.cs +++ b/shadowsocks-csharp/Controller/PACServer.cs @@ -8,6 +8,7 @@ using System.IO.Compression; using System.Net; using System.Net.Sockets; using System.Text; +using System.Text.RegularExpressions; namespace Shadowsocks.Controller { @@ -22,6 +23,10 @@ namespace Shadowsocks.Controller public event EventHandler PACFileChanged; + public event EventHandler UpdatePACFromGFWListCompleted; + + public event ErrorEventHandler UpdatePACFromGFWListError; + public void Start(Configuration configuration) { try @@ -137,7 +142,7 @@ namespace Shadowsocks.Controller using (GZipStream input = new GZipStream(new MemoryStream(pacGZ), CompressionMode.Decompress, false)) { - while((n = input.Read(buffer, 0, buffer.Length)) > 0) + while ((n = input.Read(buffer, 0, buffer.Length)) > 0) { sb.Write(buffer, 0, n); } @@ -242,5 +247,117 @@ Connection: Close //} return proxy; } + + public void UpdatePACFromGFWList() + { + GfwListUpdater gfwlist = new GfwListUpdater(); + gfwlist.DownloadCompleted += gfwlist_DownloadCompleted; + gfwlist.Error += gfwlist_Error; + gfwlist.proxy = new WebProxy(IPAddress.Loopback.ToString(), 8123); /* use polipo proxy*/ + gfwlist.Download(); + } + + private void gfwlist_DownloadCompleted(object sender, GfwListUpdater.GfwListDownloadCompletedArgs e) + { + GfwListUpdater.Parser parser = new GfwListUpdater.Parser(e.Content); + string[] lines = parser.GetValidLines(); + StringBuilder rules = new StringBuilder(lines.Length * 16); + SerializeRules(lines, rules); + string abpContent = GetAbpContent(); + abpContent = abpContent.Replace("__RULES__", rules.ToString()); + File.WriteAllText(PAC_FILE, abpContent, Encoding.UTF8); + if (UpdatePACFromGFWListCompleted != null) + { + UpdatePACFromGFWListCompleted(this, new EventArgs()); + } + } + + private void gfwlist_Error(object sender, ErrorEventArgs e) + { + if (UpdatePACFromGFWListError != null) + { + UpdatePACFromGFWListError(this, e); + } + } + + private string GetAbpContent() + { + string content; + if (File.Exists(PAC_FILE)) + { + content = File.ReadAllText(PAC_FILE, Encoding.UTF8); + Regex regex = new Regex("var\\s+rules\\s*=\\s*(\\[(\\s*\"[^\"]*\"\\s*,)*(\\s*\"[^\"]*\")\\s*\\])", RegexOptions.Singleline); + Match m = regex.Match(content); + if (m.Success) + { + content = regex.Replace(content, "var rules = __RULES__"); + return content; + } + } + byte[] abpGZ = Resources.abp_js; + byte[] buffer = new byte[1024]; // builtin pac gzip size: maximum 100K + int n; + using (MemoryStream sb = new MemoryStream()) + { + using (GZipStream input = new GZipStream(new MemoryStream(abpGZ), + CompressionMode.Decompress, false)) + { + while ((n = input.Read(buffer, 0, buffer.Length)) > 0) + { + sb.Write(buffer, 0, n); + } + } + content = System.Text.Encoding.UTF8.GetString(sb.ToArray()); + } + return content; + } + + private static void SerializeRules(string[] rules, StringBuilder builder) + { + builder.Append("[\n"); + + bool first = true; + foreach (string rule in rules) + { + if (!first) + builder.Append(",\n"); + + SerializeString(rule, builder); + + first = false; + } + + builder.Append("\n]"); + } + + private static void SerializeString(string aString, StringBuilder builder) + { + builder.Append("\t\""); + + char[] charArray = aString.ToCharArray(); + for (int i = 0; i < charArray.Length; i++) + { + char c = charArray[i]; + if (c == '"') + builder.Append("\\\""); + else if (c == '\\') + builder.Append("\\\\"); + else if (c == '\b') + builder.Append("\\b"); + else if (c == '\f') + builder.Append("\\f"); + else if (c == '\n') + builder.Append("\\n"); + else if (c == '\r') + builder.Append("\\r"); + else if (c == '\t') + builder.Append("\\t"); + else + builder.Append(c); + } + + builder.Append("\""); + } + } } diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 080390be..9b2b9ba3 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -38,6 +38,10 @@ namespace Shadowsocks.Controller // when user clicked Edit PAC, and PAC file has already created public event EventHandler PACFileReadyToOpen; + public event EventHandler UpdatePACFromGFWListCompleted; + + public event ErrorEventHandler UpdatePACFromGFWListError; + public event ErrorEventHandler Errored; public ShadowsocksController() @@ -128,6 +132,11 @@ namespace Shadowsocks.Controller { polipoRunner.Stop(); } + if (pacServer != null) + { + pacServer.Stop(); + pacServer = null; + } if (_config.enabled) { SystemProxy.Disable(); @@ -151,6 +160,18 @@ namespace Shadowsocks.Controller return "ss://" + base64; } + public void UpdatePACFromGFWList() + { + if (pacServer != null) + { + pacServer.UpdatePACFromGFWList(); + } + else if (UpdatePACFromGFWListError != null) + { + UpdatePACFromGFWListError(this, new ErrorEventArgs(new Exception("The PACServer is not run."))); + } + } + protected void Reload() { // some logic in configuration updated the config when saving, we need to read it again @@ -164,6 +185,8 @@ namespace Shadowsocks.Controller { pacServer = new PACServer(); pacServer.PACFileChanged += pacServer_PACFileChanged; + pacServer.UpdatePACFromGFWListCompleted += pacServer_UpdatePACFromGFWListCompleted; + pacServer.UpdatePACFromGFWListError += pacServer_UpdatePACFromGFWListError; } pacServer.Stop(); @@ -242,6 +265,18 @@ namespace Shadowsocks.Controller UpdateSystemProxy(); } + private void pacServer_UpdatePACFromGFWListCompleted(object sender, EventArgs e) + { + if (UpdatePACFromGFWListCompleted != null) + UpdatePACFromGFWListCompleted(this, e); + } + + private void pacServer_UpdatePACFromGFWListError(object sender, ErrorEventArgs e) + { + if (UpdatePACFromGFWListError != null) + UpdatePACFromGFWListError(this, e); + } + private void StartReleasingMemory() { _ramThread = new Thread(new ThreadStart(ReleaseMemory)); diff --git a/shadowsocks-csharp/Data/abp.js.gz b/shadowsocks-csharp/Data/abp.js.gz new file mode 100644 index 00000000..d265cfd6 Binary files /dev/null and b/shadowsocks-csharp/Data/abp.js.gz differ diff --git a/shadowsocks-csharp/Data/cn.txt b/shadowsocks-csharp/Data/cn.txt index ff77ebde..5dd129c0 100644 --- a/shadowsocks-csharp/Data/cn.txt +++ b/shadowsocks-csharp/Data/cn.txt @@ -39,3 +39,7 @@ Shadowsocks is here=Shadowsocks 在这里 You can turn on/off Shadowsocks in the context menu=可以在右键菜单中开关 Shadowsocks Enabled=已启用代理 Disabled=已禁用代理 +Update PAC File via gfwlist...=基于 gfwlist 更新 PAC 文件... +Update PAC file failed=更新 PAC 文件失败 +Update PAC file succeed=更新 PAC 文件成功 +Job already running...=任务已经运行... diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 716e0df7..c3c0537a 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -45,6 +45,8 @@ namespace Shadowsocks controller.Start(); Application.Run(); + + controller.Stop(); } } } diff --git a/shadowsocks-csharp/Properties/Resources.Designer.cs b/shadowsocks-csharp/Properties/Resources.Designer.cs index 25ea71a9..5139429f 100755 --- a/shadowsocks-csharp/Properties/Resources.Designer.cs +++ b/shadowsocks-csharp/Properties/Resources.Designer.cs @@ -61,6 +61,16 @@ namespace Shadowsocks.Properties { } /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] abp_js { + get { + object obj = ResourceManager.GetObject("abp_js", resourceCulture); + return ((byte[])(obj)); + } + } + + /// /// Looks up a localized string similar to Shadowsocks=Shadowsocks ///Enable=启用代理 ///Mode=代理模式 @@ -68,7 +78,7 @@ namespace Shadowsocks.Properties { ///Global=全局模式 ///Servers=服务器选择 ///Edit Servers...=编辑服务器... - ///Start on Boot=自动启动 + ///Start on Boot=开机启动 ///Share over LAN=在局域网共享代理 ///Edit PAC File...=编辑 PAC 文件... ///Show QRCode...=显示二维码... diff --git a/shadowsocks-csharp/Properties/Resources.resx b/shadowsocks-csharp/Properties/Resources.resx index d8fb470d..ee5f98ea 100755 --- a/shadowsocks-csharp/Properties/Resources.resx +++ b/shadowsocks-csharp/Properties/Resources.resx @@ -118,6 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Data\abp.js.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\data\cn.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index a22a8b05..b7ce3f2d 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -33,6 +33,8 @@ namespace Shadowsocks.View private MenuItem PACModeItem; private ConfigForm configForm; + private bool isUpdatePACFromGFWListRunning = false; + public MenuViewController(ShadowsocksController controller) { this.controller = controller; @@ -45,6 +47,8 @@ namespace Shadowsocks.View controller.ShareOverLANStatusChanged += controller_ShareOverLANStatusChanged; controller.EnableGlobalChanged += controller_EnableGlobalChanged; controller.Errored += controller_Errored; + controller.UpdatePACFromGFWListCompleted += controller_UpdatePACFromGFWListCompleted; + controller.UpdatePACFromGFWListError += controller_UpdatePACFromGFWListError; _notifyIcon = new NotifyIcon(); UpdateTrayIcon(); @@ -138,6 +142,7 @@ namespace Shadowsocks.View this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), this.ShareOverLANItem = CreateMenuItem("Share over LAN", new EventHandler(this.ShareOverLANItem_Click)), CreateMenuItem("Edit PAC File...", new EventHandler(this.EditPACFileItem_Click)), + CreateMenuItem("Update PAC File via gfwlist...", new EventHandler(this.UpdatePACFromGFWListItem_Click)), new MenuItem("-"), CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), @@ -176,6 +181,25 @@ namespace Shadowsocks.View System.Diagnostics.Process.Start("explorer.exe", argument); } + void controller_UpdatePACFromGFWListError(object sender, System.IO.ErrorEventArgs e) + { + isUpdatePACFromGFWListRunning = false; + _notifyIcon.BalloonTipTitle = I18N.GetString("Update PAC File via gfwlist..."); + _notifyIcon.BalloonTipText = I18N.GetString("Update PAC file failed"); + _notifyIcon.BalloonTipIcon = ToolTipIcon.Info; + _notifyIcon.ShowBalloonTip(5000); + Logging.LogUsefulException(e.GetException()); + } + + void controller_UpdatePACFromGFWListCompleted(object sender, EventArgs e) + { + isUpdatePACFromGFWListRunning = false; + _notifyIcon.BalloonTipTitle = I18N.GetString("Update PAC File via gfwlist..."); + _notifyIcon.BalloonTipText = I18N.GetString("Update PAC file succeed"); + _notifyIcon.BalloonTipIcon = ToolTipIcon.Info; + _notifyIcon.ShowBalloonTip(5000); + } + void updateChecker_NewVersionFound(object sender, EventArgs e) { _notifyIcon.BalloonTipTitle = String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber); @@ -311,6 +335,26 @@ namespace Shadowsocks.View controller.TouchPACFile(); } + private void UpdatePACFromGFWListItem_Click(object sender, EventArgs e) + { + if (isUpdatePACFromGFWListRunning) + { + _notifyIcon.BalloonTipTitle = I18N.GetString("Update PAC File via gfwlist..."); + _notifyIcon.BalloonTipText = I18N.GetString("Job already running..."); + _notifyIcon.BalloonTipIcon = ToolTipIcon.Info; + _notifyIcon.ShowBalloonTip(5000); + } + else + { + isUpdatePACFromGFWListRunning = true; + _notifyIcon.BalloonTipTitle = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version; + _notifyIcon.BalloonTipText = I18N.GetString("Update PAC File via gfwlist..."); + _notifyIcon.BalloonTipIcon = ToolTipIcon.Info; + _notifyIcon.ShowBalloonTip(5000); + controller.UpdatePACFromGFWList(); + } + } + private void AServerItem_Click(object sender, EventArgs e) { MenuItem item = (MenuItem)sender; diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index e044d4fb..1848edd9 100755 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -86,6 +86,7 @@ + @@ -101,6 +102,11 @@ + + True + True + Resources.resx + Form @@ -127,14 +133,9 @@ ResXFileCodeGenerator - Resources.Designer.cs Designer + Resources.Designer.cs - - True - Resources.resx - True - QRCodeForm.cs @@ -142,6 +143,7 @@ Designer +