@@ -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<GfwListDownloadCompletedArgs> 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<string> valid_lines = new List<string>(lines.Length); | |||
foreach (string line in lines) | |||
{ | |||
if (line.StartsWith("!") || line.StartsWith("[")) | |||
continue; | |||
valid_lines.Add(line); | |||
} | |||
return valid_lines.ToArray(); | |||
} | |||
} | |||
} | |||
} |
@@ -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("\""); | |||
} | |||
} | |||
} |
@@ -38,6 +38,10 @@ namespace Shadowsocks.Controller | |||
// when user clicked Edit PAC, and PAC file has already created | |||
public event EventHandler<PathEventArgs> 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)); | |||
@@ -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...=任务已经运行... |
@@ -45,6 +45,8 @@ namespace Shadowsocks | |||
controller.Start(); | |||
Application.Run(); | |||
controller.Stop(); | |||
} | |||
} | |||
} | |||
@@ -61,6 +61,16 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] abp_js { | |||
get { | |||
object obj = ResourceManager.GetObject("abp_js", resourceCulture); | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// 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...=显示二维码... | |||
@@ -118,6 +118,9 @@ | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||
<data name="abp_js" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\abp.js.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="cn" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\cn.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||
</data> | |||
@@ -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; | |||
@@ -86,6 +86,7 @@ | |||
<Compile Include="3rd\zxing\Version.cs" /> | |||
<Compile Include="Controller\AutoStartup.cs" /> | |||
<Compile Include="Controller\FileManager.cs" /> | |||
<Compile Include="Controller\GfwListUpdater.cs" /> | |||
<Compile Include="Controller\I18N.cs" /> | |||
<Compile Include="Controller\Logging.cs" /> | |||
<Compile Include="Controller\UpdateChecker.cs" /> | |||
@@ -101,6 +102,11 @@ | |||
<Compile Include="Controller\PACServer.cs" /> | |||
<Compile Include="Model\Server.cs" /> | |||
<Compile Include="Model\Configuration.cs" /> | |||
<Compile Include="Properties\Resources.Designer.cs"> | |||
<AutoGen>True</AutoGen> | |||
<DesignTime>True</DesignTime> | |||
<DependentUpon>Resources.resx</DependentUpon> | |||
</Compile> | |||
<Compile Include="Util\Util.cs" /> | |||
<Compile Include="View\ConfigForm.cs"> | |||
<SubType>Form</SubType> | |||
@@ -127,14 +133,9 @@ | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="Properties\Resources.resx"> | |||
<Generator>ResXFileCodeGenerator</Generator> | |||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||
<SubType>Designer</SubType> | |||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||
</EmbeddedResource> | |||
<Compile Include="Properties\Resources.Designer.cs"> | |||
<AutoGen>True</AutoGen> | |||
<DependentUpon>Resources.resx</DependentUpon> | |||
<DesignTime>True</DesignTime> | |||
</Compile> | |||
<EmbeddedResource Include="View\QRCodeForm.resx"> | |||
<DependentUpon>QRCodeForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
@@ -142,6 +143,7 @@ | |||
<None Include="app.manifest"> | |||
<SubType>Designer</SubType> | |||
</None> | |||
<None Include="Data\abp.js.gz" /> | |||
<None Include="Data\libsscrypto.dll.gz" /> | |||
<None Include="Data\polipo.exe.gz" /> | |||
<None Include="Data\proxy.pac.txt.gz" /> | |||