diff --git a/README.md b/README.md index 3304cffc..05c1dfd0 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ about the change automatically 6. Please disable other proxy addons in your browser, or set them to use system proxy +### Develop + +Visual Studio Express 2012 is recommended. + #### License GPLv3 diff --git a/shadowsocks-csharp/3rd/SimpleJson.cs b/shadowsocks-csharp/3rd/SimpleJson.cs index 2850137d..6581e84b 100644 --- a/shadowsocks-csharp/3rd/SimpleJson.cs +++ b/shadowsocks-csharp/3rd/SimpleJson.cs @@ -1035,13 +1035,13 @@ namespace SimpleJson protected static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) { - builder.Append("["); + builder.Append("[\r\n "); bool first = true; foreach (object value in anArray) { if (!first) - builder.Append(","); + builder.Append(",\r\n "); if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; @@ -1049,7 +1049,7 @@ namespace SimpleJson first = false; } - builder.Append("]"); + builder.Append("\r\n]"); return true; } diff --git a/shadowsocks-csharp/Controller/GfwListUpdater.cs b/shadowsocks-csharp/Controller/GfwListUpdater.cs new file mode 100644 index 00000000..7abe9445 --- /dev/null +++ b/shadowsocks-csharp/Controller/GfwListUpdater.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.IO; +using Shadowsocks.Properties; +using SimpleJson; +using Shadowsocks.Util; + +namespace Shadowsocks.Controller +{ + public class GFWListUpdater + { + private const string GFWLIST_URL = "https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt"; + + private static string PAC_FILE = PACServer.PAC_FILE; + + public event EventHandler UpdateCompleted; + + public event ErrorEventHandler Error; + + private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) + { + try + { + List lines = ParseResult(e.Result); + + string abpContent = Utils.UnGzip(Resources.abp_js); + abpContent = abpContent.Replace("__RULES__", SimpleJson.SimpleJson.SerializeObject(lines)); + File.WriteAllText(PAC_FILE, abpContent, Encoding.UTF8); + if (UpdateCompleted != null) + { + UpdateCompleted(this, new EventArgs()); + } + } + catch (Exception ex) + { + if (Error != null) + { + Error(this, new ErrorEventArgs(ex)); + } + } + } + + public void UpdatePACFromGFWList() + { + WebClient http = new WebClient(); + http.Proxy = new WebProxy(IPAddress.Loopback.ToString(), 8123); + http.DownloadStringCompleted += http_DownloadStringCompleted; + http.DownloadStringAsync(new Uri(GFWLIST_URL)); + } + + public List ParseResult(string response) + { + byte[] bytes = Convert.FromBase64String(response); + string content = Encoding.ASCII.GetString(bytes); + 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; + } + } +} \ No newline at end of file diff --git a/shadowsocks-csharp/Controller/Logging.cs b/shadowsocks-csharp/Controller/Logging.cs index 22b5b9b1..81f16dd8 100755 --- a/shadowsocks-csharp/Controller/Logging.cs +++ b/shadowsocks-csharp/Controller/Logging.cs @@ -17,8 +17,7 @@ namespace Shadowsocks.Controller string temppath = Path.GetTempPath(); LogFile = Path.Combine(temppath, "shadowsocks.log"); FileStream fs = new FileStream(LogFile, FileMode.Append); - TextWriter tmp = Console.Out; - StreamWriter sw = new StreamWriter(fs); + StreamWriterWithTimestamp sw = new StreamWriterWithTimestamp(fs); sw.AutoFlush = true; Console.SetOut(sw); Console.SetError(sw); @@ -61,5 +60,30 @@ namespace Shadowsocks.Controller Console.WriteLine(e); } } + + } + + // Simply extended System.IO.StreamWriter for adding timestamp workaround + public class StreamWriterWithTimestamp : StreamWriter + { + public StreamWriterWithTimestamp(Stream stream) : base(stream) + { + } + + private string GetTimestamp() + { + return "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] "; + } + + public override void WriteLine(string value) + { + base.WriteLine(GetTimestamp() + value); + } + + public override void Write(string value) + { + base.Write(GetTimestamp() + value); + } } + } diff --git a/shadowsocks-csharp/Controller/PACServer.cs b/shadowsocks-csharp/Controller/PACServer.cs index f02476ae..b2456e18 100755 --- a/shadowsocks-csharp/Controller/PACServer.cs +++ b/shadowsocks-csharp/Controller/PACServer.cs @@ -1,5 +1,6 @@ using Shadowsocks.Model; using Shadowsocks.Properties; +using Shadowsocks.Util; using System; using System.Collections.Generic; using System.Diagnostics; @@ -14,7 +15,7 @@ namespace Shadowsocks.Controller class PACServer { private static int PORT = 8093; - private static string PAC_FILE = "pac.txt"; + public static string PAC_FILE = "pac.txt"; private static Configuration config; Socket _listener; @@ -130,19 +131,7 @@ namespace Shadowsocks.Controller } else { - byte[] pacGZ = Resources.proxy_pac_txt; - byte[] buffer = new byte[1024]; // builtin pac gzip size: maximum 100K - MemoryStream sb = new MemoryStream(); - int n; - using (GZipStream input = new GZipStream(new MemoryStream(pacGZ), - CompressionMode.Decompress, false)) - { - while((n = input.Read(buffer, 0, buffer.Length)) > 0) - { - sb.Write(buffer, 0, n); - } - return System.Text.Encoding.UTF8.GetString(sb.ToArray()); - } + return Utils.UnGzip(Resources.proxy_pac_txt); } } @@ -175,7 +164,7 @@ Connection: Close ", System.Text.Encoding.UTF8.GetBytes(pac).Length) + pac; byte[] response = System.Text.Encoding.UTF8.GetBytes(text); conn.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), conn); - Util.Util.ReleaseMemory(); + Util.Utils.ReleaseMemory(); } else { diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 080390be..1e193158 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -21,6 +21,7 @@ namespace Shadowsocks.Controller private PACServer pacServer; private Configuration _config; private PolipoRunner polipoRunner; + private GFWListUpdater gfwListUpdater; private bool stopped = false; private bool _systemProxyIsDirty = false; @@ -38,6 +39,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() @@ -151,6 +156,14 @@ namespace Shadowsocks.Controller return "ss://" + base64; } + public void UpdatePACFromGFWList() + { + if (gfwListUpdater != null) + { + gfwListUpdater.UpdatePACFromGFWList(); + } + } + protected void Reload() { // some logic in configuration updated the config when saving, we need to read it again @@ -165,6 +178,12 @@ namespace Shadowsocks.Controller pacServer = new PACServer(); pacServer.PACFileChanged += pacServer_PACFileChanged; } + if (gfwListUpdater == null) + { + gfwListUpdater = new GFWListUpdater(); + gfwListUpdater.UpdateCompleted += pacServer_PACUpdateCompleted; + gfwListUpdater.Error += pacServer_PACUpdateError; + } pacServer.Stop(); @@ -208,7 +227,7 @@ namespace Shadowsocks.Controller } UpdateSystemProxy(); - Util.Util.ReleaseMemory(); + Util.Utils.ReleaseMemory(); } @@ -242,6 +261,18 @@ namespace Shadowsocks.Controller UpdateSystemProxy(); } + private void pacServer_PACUpdateCompleted(object sender, EventArgs e) + { + if (UpdatePACFromGFWListCompleted != null) + UpdatePACFromGFWListCompleted(this, e); + } + + private void pacServer_PACUpdateError(object sender, ErrorEventArgs e) + { + if (UpdatePACFromGFWListError != null) + UpdatePACFromGFWListError(this, e); + } + private void StartReleasingMemory() { _ramThread = new Thread(new ThreadStart(ReleaseMemory)); @@ -253,7 +284,7 @@ namespace Shadowsocks.Controller { while (true) { - Util.Util.ReleaseMemory(); + Util.Utils.ReleaseMemory(); Thread.Sleep(30 * 1000); } } diff --git a/shadowsocks-csharp/Controller/UpdateChecker.cs b/shadowsocks-csharp/Controller/UpdateChecker.cs index e71d95c9..7996a687 100755 --- a/shadowsocks-csharp/Controller/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/UpdateChecker.cs @@ -23,6 +23,7 @@ namespace Shadowsocks.Controller { // TODO test failures WebClient http = new WebClient(); + http.Proxy = new WebProxy(IPAddress.Loopback.ToString(), 8123); http.DownloadStringCompleted += http_DownloadStringCompleted; http.DownloadStringAsync(new Uri(UpdateURL)); } @@ -145,7 +146,7 @@ namespace Shadowsocks.Controller } catch (Exception ex) { - Console.Write(ex.ToString()); + Console.WriteLine(ex.ToString()); return; } } diff --git a/shadowsocks-csharp/Data/abp.js.gz b/shadowsocks-csharp/Data/abp.js.gz new file mode 100755 index 00000000..e049c0a4 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 78f5d19e..cd9dfff9 100644 --- a/shadowsocks-csharp/Data/cn.txt +++ b/shadowsocks-csharp/Data/cn.txt @@ -4,8 +4,8 @@ Mode=代理模式 PAC=PAC 模式 Global=全局模式 Servers=服务器选择 -Edit Servers...=编辑Shadowsocks服务器... -Start on Boot=开机启动 +Edit Servers...=编辑Shadowsocks服务器... +Start on Boot=开机启动 Share over LAN=在局域网共享代理 Edit PAC File...=编辑 PAC 文件... Show QRCode...=显示二维码... @@ -39,6 +39,9 @@ Shadowsocks is here=Shadowsocks 在这里 You can turn on/off Shadowsocks in the context menu=可以在右键菜单中开关 Shadowsocks Enabled=已启用代理 Disabled=已禁用代理 +Update PAC from GFWList=从 GFWList 更新 PAC +Failed to update PAC file =更新 PAC 文件失败 +PAC updated=更新 PAC 成功 &Parse=解析(&P) Shadowsocks URI=Shadowsocks URI Shadowsocks URI Parse=Shadowsocks URI解析 diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 716e0df7..8ca485d7 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -18,7 +18,7 @@ namespace Shadowsocks [STAThread] static void Main() { - Util.Util.ReleaseMemory(); + Util.Utils.ReleaseMemory(); using (Mutex mutex = new Mutex(false, "Global\\" + "71981632-A427-497F-AB91-241CD227EC1F")) { Application.EnableVisualStyles(); 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/Util/Util.cs b/shadowsocks-csharp/Util/Util.cs index afb5539a..15463a3a 100755 --- a/shadowsocks-csharp/Util/Util.cs +++ b/shadowsocks-csharp/Util/Util.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; namespace Shadowsocks.Util { - public class Util + public class Utils { public static void ReleaseMemory() { @@ -22,6 +24,24 @@ namespace Shadowsocks.Util (UIntPtr)0xFFFFFFFF, (UIntPtr)0xFFFFFFFF); } + public static string UnGzip(byte[] buf) + { + byte[] buffer = new byte[1024]; + int n; + using (MemoryStream sb = new MemoryStream()) + { + using (GZipStream input = new GZipStream(new MemoryStream(buf), + CompressionMode.Decompress, false)) + { + while ((n = input.Read(buffer, 0, buffer.Length)) > 0) + { + sb.Write(buffer, 0, n); + } + } + return System.Text.Encoding.UTF8.GetString(sb.ToArray()); + } + } + [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetProcessWorkingSetSize(IntPtr process, diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index 8dae0dbb..43a7b8f5 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -45,6 +45,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 +140,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 from 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,19 +179,36 @@ namespace Shadowsocks.View System.Diagnostics.Process.Start("explorer.exe", argument); } + void ShowBalloonTip(string title, string content, ToolTipIcon icon, int timeout) + { + _notifyIcon.BalloonTipTitle = title; + _notifyIcon.BalloonTipText = content; + _notifyIcon.BalloonTipIcon = icon; + _notifyIcon.ShowBalloonTip(timeout); + } + + void controller_UpdatePACFromGFWListError(object sender, System.IO.ErrorEventArgs e) + { + ShowBalloonTip(I18N.GetString("Failed to update PAC file"), e.GetException().Message, ToolTipIcon.Error, 5000); + Logging.LogUsefulException(e.GetException()); + } + + void controller_UpdatePACFromGFWListCompleted(object sender, EventArgs e) + { + ShowBalloonTip(I18N.GetString("Shadowsocks"), I18N.GetString("PAC updated"), ToolTipIcon.Info, 1000); + } + void updateChecker_NewVersionFound(object sender, EventArgs e) { - _notifyIcon.BalloonTipTitle = String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber); - _notifyIcon.BalloonTipText = I18N.GetString("Click here to download"); - _notifyIcon.BalloonTipIcon = ToolTipIcon.Info; + ShowBalloonTip(String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber), I18N.GetString("Click here to download"), ToolTipIcon.Info, 5000); _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; - _notifyIcon.ShowBalloonTip(5000); _isFirstRun = false; } void notifyIcon1_BalloonTipClicked(object sender, EventArgs e) { System.Diagnostics.Process.Start(updateChecker.LatestVersionURL); + _notifyIcon.BalloonTipClicked -= notifyIcon1_BalloonTipClicked; } @@ -245,7 +265,7 @@ namespace Shadowsocks.View void configForm_FormClosed(object sender, FormClosedEventArgs e) { configForm = null; - Util.Util.ReleaseMemory(); + Util.Utils.ReleaseMemory(); ShowFirstTimeBalloon(); } @@ -312,6 +332,11 @@ namespace Shadowsocks.View controller.TouchPACFile(); } + private void UpdatePACFromGFWListItem_Click(object sender, EventArgs e) + { + 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 67382138..3ee4a106 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 @@ -133,14 +139,9 @@ ResXFileCodeGenerator - Resources.Designer.cs Designer + Resources.Designer.cs - - True - Resources.resx - True - QRCodeForm.cs @@ -151,6 +152,7 @@ Designer +