diff --git a/shadowsocks-csharp/Controller/System/SystemProxy.cs b/shadowsocks-csharp/Controller/System/SystemProxy.cs index 1d61dd86..025781b7 100644 --- a/shadowsocks-csharp/Controller/System/SystemProxy.cs +++ b/shadowsocks-csharp/Controller/System/SystemProxy.cs @@ -31,7 +31,7 @@ namespace Shadowsocks.Controller { if (global) { - Sysproxy.SetIEProxy(true, true, "localhost:" + config.localPort.ToString(), null); + WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), ""); } else { @@ -45,12 +45,12 @@ namespace Shadowsocks.Controller pacUrl = pacSrv.PacUrl; } - Sysproxy.SetIEProxy(true, false, null, pacUrl); + WinINet.ProxyPAC(pacUrl); } } else { - Sysproxy.SetIEProxy(false, false, null, null); + WinINet.Restore(); } } catch (ProxyException ex) @@ -61,7 +61,7 @@ namespace Shadowsocks.Controller var ret = MessageBox.Show(I18N.GetString("Error occured when process proxy setting, do you want reset current setting and retry?"), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (ret == DialogResult.Yes) { - Sysproxy.ResetIEProxy(); + WinINet.Reset(); Update(config, forceDisable, pacSrv, true); } } diff --git a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs b/shadowsocks-csharp/Util/SystemProxy/WinINet.cs index 0c8bc700..d33d3b5a 100644 --- a/shadowsocks-csharp/Util/SystemProxy/WinINet.cs +++ b/shadowsocks-csharp/Util/SystemProxy/WinINet.cs @@ -1,5 +1,9 @@ -using System; +using Newtonsoft.Json; +using Shadowsocks.Model; +using System; using System.Collections.Generic; +using System.ComponentModel; +using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -114,16 +118,29 @@ namespace Shadowsocks.Util.SystemProxy } } + public class WinINetSetting + { + public InternetPerConnectionFlags Flags = InternetPerConnectionFlags.Direct; + public string ProxyServer; + public string ProxyBypass; + public string AutoConfigUrl; + } + public class WinINet { - // TODO: Save, Restore, - // TODO: Query, Set, + private const string SettingFile = "wininet-setting.json"; + private static WinINetSetting initialSetting; + + static WinINet() + { + Load(); + } public static void ProxyGlobal(string server, string bypass) { List options = new List { - GetOption(InternetPerConnectionOptionEnum.FlagsUI,InternetPerConnectionFlags.Proxy), + GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.Proxy|InternetPerConnectionFlags.Direct), GetOption(InternetPerConnectionOptionEnum.ProxyServer,server), GetOption(InternetPerConnectionOptionEnum.ProxyBypass,bypass), }; @@ -133,8 +150,8 @@ namespace Shadowsocks.Util.SystemProxy { List options = new List { - GetOption(InternetPerConnectionOptionEnum.FlagsUI,InternetPerConnectionFlags.AutoProxyUrl), - GetOption(InternetPerConnectionOptionEnum.ProxyServer,url), + GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.AutoProxyUrl|InternetPerConnectionFlags.Direct), + GetOption(InternetPerConnectionOptionEnum.AutoConfigUrl,url), }; Exec(options); } @@ -142,10 +159,124 @@ namespace Shadowsocks.Util.SystemProxy { List options = new List { - GetOption(InternetPerConnectionOptionEnum.FlagsUI,InternetPerConnectionFlags.Direct), + GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.Direct), + }; + Exec(options); + } + + private static void Load() + { + try + { + string configContent = File.ReadAllText(Utils.GetTempPath(SettingFile)); + initialSetting = JsonConvert.DeserializeObject(configContent); + } + catch (Exception) + { + // Suppress all exceptions. finally block will initialize new user config settings. + } + finally + { + initialSetting ??= new WinINetSetting(); + } + } + private static void Save() + { + try + { + using (StreamWriter sw = new StreamWriter(File.Open(Utils.GetTempPath(SettingFile), FileMode.Create))) + { + string jsonString = JsonConvert.SerializeObject(initialSetting, Formatting.Indented); + sw.Write(jsonString); + sw.Flush(); + } + } + catch (IOException e) + { + // logger.LogUsefulException(e); + } + } + private static void Record() + { + initialSetting ??= Query(); + } + public static void Restore() + { + Set(initialSetting); + } + + public static void Set(WinINetSetting setting) + { + List options = new List + { + GetOption(InternetPerConnectionOptionEnum.Flags,setting.Flags), + GetOption(InternetPerConnectionOptionEnum.ProxyServer,setting.ProxyServer), + GetOption(InternetPerConnectionOptionEnum.ProxyBypass,setting.ProxyBypass), + GetOption(InternetPerConnectionOptionEnum.AutoConfigUrl,setting.AutoConfigUrl), }; Exec(options); } + public static void Reset() + { + Set(new WinINetSetting + { + Flags = InternetPerConnectionFlags.Direct, + ProxyServer = "", + ProxyBypass = "", + AutoConfigUrl = "", + }); + } + + public static WinINetSetting Query() + { + List options = new List + { + new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.FlagsUI}, + new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.ProxyServer}, + new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.ProxyBypass}, + new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.AutoConfigUrl}, + }; + + var (unmanagedList, listSize) = PrepareOptionList(options, null); + bool ok = InternetQueryOption(IntPtr.Zero, (int)InternetOptions.PerConnectionOption, unmanagedList, ref listSize); + if (!ok) throw new Exception(); + + var proxy = new WinINetSetting(); + + InternetPerConnectionOptionList ret = Marshal.PtrToStructure(unmanagedList); + IntPtr p = ret.pOptions; + int nOption = ret.OptionCount; + List outOptions = new List(); + for (int i = 0; i < nOption; i++) + { + var o = Marshal.PtrToStructure(p); + outOptions.Add(o); + p += Marshal.SizeOf(o); + } + + foreach (var o in outOptions) + { + switch ((InternetPerConnectionOptionEnum)o.dwOption) + { + case InternetPerConnectionOptionEnum.FlagsUI: + case InternetPerConnectionOptionEnum.Flags: + proxy.Flags = (InternetPerConnectionFlags)o.Value.dwValue; + break; + case InternetPerConnectionOptionEnum.AutoConfigUrl: + proxy.AutoConfigUrl = Marshal.PtrToStringAuto(o.Value.pszValue); + break; + case InternetPerConnectionOptionEnum.ProxyBypass: + proxy.ProxyBypass = Marshal.PtrToStringAuto(o.Value.pszValue); + break; + case InternetPerConnectionOptionEnum.ProxyServer: + proxy.ProxyServer = Marshal.PtrToStringAuto(o.Value.pszValue); + break; + default: + break; + } + } + return proxy; + } private static InternetPerConnectionOption GetOption( InternetPerConnectionOptionEnum option, @@ -177,16 +308,7 @@ namespace Shadowsocks.Util.SystemProxy }; } - private static void Exec(List options) - { - Exec(options, null); - foreach (var conn in RAS.GetAllConnections()) - { - Exec(options, conn); - } - } - - private static void Exec(List options, string connName) + private static (IntPtr, int) PrepareOptionList(List options, string connName) { int len = options.Sum(o => Marshal.SizeOf(o)); @@ -201,10 +323,10 @@ namespace Shadowsocks.Util.SystemProxy InternetPerConnectionOptionList optionList = new InternetPerConnectionOptionList { pOptions = buf, + OptionCount = options.Count, Connection = string.IsNullOrEmpty(connName) ? IntPtr.Zero : Marshal.StringToHGlobalAuto(connName), - OptionCount = options.Count, OptionError = 0, }; int listSize = Marshal.SizeOf(optionList); @@ -212,6 +334,34 @@ namespace Shadowsocks.Util.SystemProxy IntPtr unmanagedList = Marshal.AllocCoTaskMem(listSize); Marshal.StructureToPtr(optionList, unmanagedList, true); + return (unmanagedList, listSize); + } + + private static void ClearOptionList(IntPtr list) + { + InternetPerConnectionOptionList l = Marshal.PtrToStructure(list); + Marshal.FreeCoTaskMem(l.pOptions); + Marshal.FreeCoTaskMem(list); + } + + private static void Exec(List options) + { + // TODO: optimize load and save + Load(); + Record(); + + Exec(options, null); + foreach (var conn in RAS.GetAllConnections()) + { + Exec(options, conn); + } + + Save(); + } + + private static void Exec(List options, string connName) + { + var (unmanagedList, listSize) = PrepareOptionList(options, connName); bool ok = InternetSetOption( IntPtr.Zero, @@ -220,10 +370,12 @@ namespace Shadowsocks.Util.SystemProxy listSize ); - Marshal.FreeCoTaskMem(buf); - Marshal.FreeCoTaskMem(unmanagedList); - - if (!ok) throw new Exception(); + if (!ok) + { + int errno = Marshal.GetLastWin32Error(); + throw new Win32Exception(errno); + } + ClearOptionList(unmanagedList); ok = InternetSetOption( IntPtr.Zero, (int)InternetOptions.ProxySettingChanged, @@ -243,6 +395,9 @@ namespace Shadowsocks.Util.SystemProxy [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength); - } + [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool InternetQueryOption(IntPtr hInternet, uint dwOption, IntPtr lpBuffer, ref int lpdwBufferLength); + + } }