@@ -10,17 +10,12 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | private static Logger logger = LogManager.GetCurrentClassLogger(); | ||||
private static string GetTimestamp(DateTime value) | |||||
{ | |||||
return value.ToString("yyyyMMddHHmmssfff"); | |||||
} | |||||
public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false) | public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false) | ||||
{ | { | ||||
bool global = config.global; | bool global = config.global; | ||||
bool enabled = config.enabled; | bool enabled = config.enabled; | ||||
if (forceDisable) | |||||
if (forceDisable || WinINet.operational) | |||||
{ | { | ||||
enabled = false; | enabled = false; | ||||
} | } | ||||
@@ -31,7 +26,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
if (global) | if (global) | ||||
{ | { | ||||
Sysproxy.SetIEProxy(true, true, "localhost:" + config.localPort.ToString(), null); | |||||
WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), "<local>"); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -45,12 +40,12 @@ namespace Shadowsocks.Controller | |||||
pacUrl = pacSrv.PacUrl; | pacUrl = pacSrv.PacUrl; | ||||
} | } | ||||
Sysproxy.SetIEProxy(true, false, null, pacUrl); | |||||
WinINet.ProxyPAC(pacUrl); | |||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
Sysproxy.SetIEProxy(false, false, null, null); | |||||
WinINet.Restore(); | |||||
} | } | ||||
} | } | ||||
catch (ProxyException ex) | catch (ProxyException ex) | ||||
@@ -61,7 +56,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); | 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) | if (ret == DialogResult.Yes) | ||||
{ | { | ||||
Sysproxy.ResetIEProxy(); | |||||
WinINet.Reset(); | |||||
Update(config, forceDisable, pacSrv, true); | Update(config, forceDisable, pacSrv, true); | ||||
} | } | ||||
} | } | ||||
@@ -151,12 +151,6 @@ | |||||
<data name="ssw128" type="System.Resources.ResXFileRef, System.Windows.Forms"> | <data name="ssw128" type="System.Resources.ResXFileRef, System.Windows.Forms"> | ||||
<value>..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | <value>..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | ||||
</data> | </data> | ||||
<data name="sysproxy64_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Data\sysproxy64.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</data> | |||||
<data name="sysproxy_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Data\sysproxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</data> | |||||
<data name="user_rule" type="System.Resources.ResXFileRef, System.Windows.Forms"> | <data name="user_rule" type="System.Resources.ResXFileRef, System.Windows.Forms"> | ||||
<value>..\data\user-rule.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | <value>..\data\user-rule.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | ||||
</data> | </data> |
@@ -0,0 +1,92 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Runtime.InteropServices; | |||||
using System.Text; | |||||
namespace Shadowsocks.Util.SystemProxy | |||||
{ | |||||
enum RasFieldSizeConst | |||||
{ | |||||
MaxEntryName = 256, | |||||
MaxPath = 260, | |||||
} | |||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | |||||
struct RasEntryName | |||||
{ | |||||
public int dwSize; | |||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS.MaxEntryName + 1)] | |||||
public string szEntryName; | |||||
public int dwFlags; | |||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS.MaxPath + 1)] | |||||
public string szPhonebookPath; | |||||
} | |||||
class RAS | |||||
{ | |||||
public const int MaxEntryName = 256; | |||||
public const int MaxPath = 260; | |||||
const int ESuccess = 0; | |||||
const int RasBase = 600; | |||||
const int EBufferTooSmall = 603; | |||||
[DllImport("rasapi32.dll", CharSet = CharSet.Auto)] | |||||
private static extern uint RasEnumEntries( | |||||
// reserved, must be NULL | |||||
string reserved, | |||||
// pointer to full path and file name of phone-book file | |||||
string lpszPhonebook, | |||||
// buffer to receive phone-book entries | |||||
[In, Out] RasEntryName[] lprasentryname, | |||||
// size in bytes of buffer | |||||
ref int lpcb, | |||||
// number of entries written to buffer | |||||
out int lpcEntries | |||||
); | |||||
public static string[] GetAllConnections() | |||||
{ | |||||
int lpNames = 0; | |||||
int entryNameSize = 0; | |||||
int lpSize = 0; | |||||
uint retval = ESuccess; | |||||
RasEntryName[] names = null; | |||||
entryNameSize = Marshal.SizeOf(typeof(RasEntryName)); | |||||
// Windows Vista or later: To determine the required buffer size, call RasEnumEntries | |||||
// with lprasentryname set to NULL. The variable pointed to by lpcb should be set to zero. | |||||
// The function will return the required buffer size in lpcb and an error code of ERROR_BUFFER_TOO_SMALL. | |||||
retval = RasEnumEntries(null, null, null, ref lpSize, out lpNames); | |||||
if (retval == EBufferTooSmall) | |||||
{ | |||||
names = new RasEntryName[lpNames]; | |||||
for (int i = 0; i < names.Length; i++) | |||||
{ | |||||
names[i].dwSize = entryNameSize; | |||||
} | |||||
retval = RasEnumEntries(null, null, names, ref lpSize, out lpNames); | |||||
} | |||||
if (retval == ESuccess) | |||||
{ | |||||
if (lpNames == 0) | |||||
{ | |||||
// no entries found. | |||||
return Array.Empty<string>(); | |||||
} | |||||
return names.Select(n => n.szEntryName).ToArray(); | |||||
} | |||||
else | |||||
{ | |||||
throw new Exception(); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -1,291 +0,0 @@ | |||||
using Newtonsoft.Json; | |||||
using NLog; | |||||
using Shadowsocks.Controller; | |||||
using Shadowsocks.Model; | |||||
using Shadowsocks.Properties; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Diagnostics; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
namespace Shadowsocks.Util.SystemProxy | |||||
{ | |||||
public static class Sysproxy | |||||
{ | |||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
private const string _userWininetConfigFile = "user-wininet.json"; | |||||
private readonly static string[] _lanIP = { | |||||
"<local>", | |||||
"localhost", | |||||
"127.*", | |||||
"10.*", | |||||
"172.16.*", | |||||
"172.17.*", | |||||
"172.18.*", | |||||
"172.19.*", | |||||
"172.20.*", | |||||
"172.21.*", | |||||
"172.22.*", | |||||
"172.23.*", | |||||
"172.24.*", | |||||
"172.25.*", | |||||
"172.26.*", | |||||
"172.27.*", | |||||
"172.28.*", | |||||
"172.29.*", | |||||
"172.30.*", | |||||
"172.31.*", | |||||
"192.168.*" | |||||
}; | |||||
private static string _queryStr; | |||||
// In general, this won't change | |||||
// format: | |||||
// <flags><CR-LF> | |||||
// <proxy-server><CR-LF> | |||||
// <bypass-list><CR-LF> | |||||
// <pac-url> | |||||
private static SysproxyConfig _userSettings = null; | |||||
enum RET_ERRORS : int | |||||
{ | |||||
RET_NO_ERROR = 0, | |||||
INVALID_FORMAT = 1, | |||||
NO_PERMISSION = 2, | |||||
SYSCALL_FAILED = 3, | |||||
NO_MEMORY = 4, | |||||
INVAILD_OPTION_COUNT = 5, | |||||
}; | |||||
static Sysproxy() | |||||
{ | |||||
try | |||||
{ | |||||
FileManager.UncompressFile(Utils.GetTempPath("sysproxy.exe"), | |||||
Environment.Is64BitOperatingSystem ? Resources.sysproxy64_exe : Resources.sysproxy_exe); | |||||
} | |||||
catch (IOException e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
public static void SetIEProxy(bool enable, bool global, string proxyServer, string pacURL) | |||||
{ | |||||
Read(); | |||||
if (!_userSettings.UserSettingsRecorded) | |||||
{ | |||||
// record user settings | |||||
ExecSysproxy("query"); | |||||
ParseQueryStr(_queryStr); | |||||
} | |||||
string arguments; | |||||
if (enable) | |||||
{ | |||||
string customBypassString = _userSettings.BypassList ?? ""; | |||||
List<string> customBypassList = new List<string>(customBypassString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); | |||||
customBypassList.AddRange(_lanIP); | |||||
string[] realBypassList = customBypassList.Distinct().ToArray(); | |||||
string realBypassString = string.Join(";", realBypassList); | |||||
arguments = global | |||||
? $"global {proxyServer} {realBypassString}" | |||||
: $"pac {pacURL}"; | |||||
} | |||||
else | |||||
{ | |||||
// restore user settings | |||||
var flags = _userSettings.Flags; | |||||
var proxy_server = _userSettings.ProxyServer ?? "-"; | |||||
var bypass_list = _userSettings.BypassList ?? "-"; | |||||
var pac_url = _userSettings.PacUrl ?? "-"; | |||||
arguments = $"set {flags} {proxy_server} {bypass_list} {pac_url}"; | |||||
// have to get new settings | |||||
_userSettings.UserSettingsRecorded = false; | |||||
} | |||||
Save(); | |||||
ExecSysproxy(arguments); | |||||
} | |||||
// set system proxy to 1 (null) (null) (null) | |||||
public static bool ResetIEProxy() | |||||
{ | |||||
try | |||||
{ | |||||
// clear user-wininet.json | |||||
_userSettings = new SysproxyConfig(); | |||||
Save(); | |||||
// clear system setting | |||||
ExecSysproxy("set 1 - - -"); | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
private static void ExecSysproxy(string arguments) | |||||
{ | |||||
// using event to avoid hanging when redirect standard output/error | |||||
// ref: https://stackoverflow.com/questions/139593/processstartinfo-hanging-on-waitforexit-why | |||||
// and http://blog.csdn.net/zhangweixing0/article/details/7356841 | |||||
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) | |||||
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) | |||||
{ | |||||
using (var process = new Process()) | |||||
{ | |||||
// Configure the process using the StartInfo properties. | |||||
process.StartInfo.FileName = Utils.GetTempPath("sysproxy.exe"); | |||||
process.StartInfo.Arguments = arguments; | |||||
process.StartInfo.WorkingDirectory = Utils.GetTempPath(); | |||||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; | |||||
process.StartInfo.UseShellExecute = false; | |||||
process.StartInfo.RedirectStandardError = true; | |||||
process.StartInfo.RedirectStandardOutput = true; | |||||
// Need to provide encoding info, or output/error strings we got will be wrong. | |||||
process.StartInfo.StandardOutputEncoding = Encoding.Unicode; | |||||
process.StartInfo.StandardErrorEncoding = Encoding.Unicode; | |||||
process.StartInfo.CreateNoWindow = true; | |||||
StringBuilder output = new StringBuilder(); | |||||
StringBuilder error = new StringBuilder(); | |||||
process.OutputDataReceived += (sender, e) => | |||||
{ | |||||
if (e.Data == null) | |||||
{ | |||||
outputWaitHandle.Set(); | |||||
} | |||||
else | |||||
{ | |||||
output.AppendLine(e.Data); | |||||
} | |||||
}; | |||||
process.ErrorDataReceived += (sender, e) => | |||||
{ | |||||
if (e.Data == null) | |||||
{ | |||||
errorWaitHandle.Set(); | |||||
} | |||||
else | |||||
{ | |||||
error.AppendLine(e.Data); | |||||
} | |||||
}; | |||||
try | |||||
{ | |||||
process.Start(); | |||||
process.BeginErrorReadLine(); | |||||
process.BeginOutputReadLine(); | |||||
process.WaitForExit(); | |||||
} | |||||
catch (System.ComponentModel.Win32Exception e) | |||||
{ | |||||
// log the arguments | |||||
throw new ProxyException(ProxyExceptionType.FailToRun, process.StartInfo.Arguments, e); | |||||
} | |||||
var stderr = error.ToString(); | |||||
var stdout = output.ToString(); | |||||
var exitCode = process.ExitCode; | |||||
if (exitCode != (int)RET_ERRORS.RET_NO_ERROR) | |||||
{ | |||||
throw new ProxyException(ProxyExceptionType.SysproxyExitError, stderr); | |||||
} | |||||
if (arguments == "query") | |||||
{ | |||||
if (stdout.IsNullOrWhiteSpace() || stdout.IsNullOrEmpty()) | |||||
{ | |||||
// we cannot get user settings | |||||
throw new ProxyException(ProxyExceptionType.QueryReturnEmpty); | |||||
} | |||||
_queryStr = stdout; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private static void Save() | |||||
{ | |||||
try | |||||
{ | |||||
using (StreamWriter sw = new StreamWriter(File.Open(Utils.GetTempPath(_userWininetConfigFile), FileMode.Create))) | |||||
{ | |||||
string jsonString = JsonConvert.SerializeObject(_userSettings, Formatting.Indented); | |||||
sw.Write(jsonString); | |||||
sw.Flush(); | |||||
} | |||||
} | |||||
catch (IOException e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private static void Read() | |||||
{ | |||||
try | |||||
{ | |||||
string configContent = File.ReadAllText(Utils.GetTempPath(_userWininetConfigFile)); | |||||
_userSettings = JsonConvert.DeserializeObject<SysproxyConfig>(configContent); | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
// Suppress all exceptions. finally block will initialize new user config settings. | |||||
} | |||||
finally | |||||
{ | |||||
if (_userSettings == null) _userSettings = new SysproxyConfig(); | |||||
} | |||||
} | |||||
private static void ParseQueryStr(string str) | |||||
{ | |||||
string[] userSettingsArr = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); | |||||
// sometimes sysproxy output in utf16le instead of ascii | |||||
// manually translate it | |||||
if (userSettingsArr.Length != 4) | |||||
{ | |||||
byte[] strByte = Encoding.ASCII.GetBytes(str); | |||||
str = Encoding.Unicode.GetString(strByte); | |||||
userSettingsArr = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); | |||||
// still fail, throw exception with string hexdump | |||||
if (userSettingsArr.Length != 4) | |||||
{ | |||||
throw new ProxyException(ProxyExceptionType.QueryReturnMalformed, BitConverter.ToString(strByte)); | |||||
} | |||||
} | |||||
_userSettings.Flags = userSettingsArr[0]; | |||||
// handle output from WinINET | |||||
if (userSettingsArr[1] == "(null)") _userSettings.ProxyServer = null; | |||||
else _userSettings.ProxyServer = userSettingsArr[1]; | |||||
if (userSettingsArr[2] == "(null)") _userSettings.BypassList = null; | |||||
else _userSettings.BypassList = userSettingsArr[2]; | |||||
if (userSettingsArr[3] == "(null)") _userSettings.PacUrl = null; | |||||
else _userSettings.PacUrl = userSettingsArr[3]; | |||||
_userSettings.UserSettingsRecorded = true; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,447 @@ | |||||
using Newtonsoft.Json; | |||||
using NLog; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.ComponentModel; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Runtime.InteropServices; | |||||
namespace Shadowsocks.Util.SystemProxy | |||||
{ | |||||
public enum InternetOptions | |||||
{ | |||||
Refresh = 37, | |||||
SettingsChanged = 39, | |||||
PerConnectionOption = 75, | |||||
ProxySettingChanged = 95, | |||||
} | |||||
public enum InternetPerConnectionOptionEnum | |||||
{ | |||||
Flags = 1, | |||||
ProxyServer = 2, | |||||
ProxyBypass = 3, | |||||
AutoConfigUrl = 4, | |||||
AutoDiscovery = 5, | |||||
AutoConfigSecondaryUrl = 6, | |||||
AutoConfigReloadDelay = 7, | |||||
AutoConfigLastDetectTime = 8, | |||||
AutoConfigLastDetectUrl = 9, | |||||
FlagsUI = 10, | |||||
} | |||||
[Flags] | |||||
public enum InternetPerConnectionFlags | |||||
{ | |||||
Direct = 0x01, | |||||
Proxy = 0x02, | |||||
AutoProxyUrl = 0x04, | |||||
AutoDetect = 0x08, | |||||
} | |||||
[StructLayout(LayoutKind.Explicit)] | |||||
public struct InternetPerConnectionOptionUnion : IDisposable | |||||
{ | |||||
[FieldOffset(0)] | |||||
public int dwValue; | |||||
[FieldOffset(0)] | |||||
public IntPtr pszValue; | |||||
[FieldOffset(0)] | |||||
public System.Runtime.InteropServices.ComTypes.FILETIME ftValue; | |||||
public void Dispose() | |||||
{ | |||||
Dispose(true); | |||||
GC.SuppressFinalize(this); | |||||
} | |||||
private void Dispose(bool disposing) | |||||
{ | |||||
if (disposing) | |||||
{ | |||||
if (pszValue != IntPtr.Zero) | |||||
{ | |||||
Marshal.FreeHGlobal(pszValue); | |||||
pszValue = IntPtr.Zero; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
[StructLayout(LayoutKind.Sequential)] | |||||
public struct InternetPerConnectionOption | |||||
{ | |||||
public int dwOption; | |||||
public InternetPerConnectionOptionUnion Value; | |||||
} | |||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | |||||
public struct InternetPerConnectionOptionList : IDisposable | |||||
{ | |||||
public int Size; | |||||
// The connection to be set. NULL means LAN. | |||||
public IntPtr Connection; | |||||
public int OptionCount; | |||||
public int OptionError; | |||||
// List of INTERNET_PER_CONN_OPTIONs. | |||||
public IntPtr pOptions; | |||||
public void Dispose() | |||||
{ | |||||
Dispose(true); | |||||
GC.SuppressFinalize(this); | |||||
} | |||||
private void Dispose(bool disposing) | |||||
{ | |||||
if (disposing) | |||||
{ | |||||
if (Connection != IntPtr.Zero) | |||||
{ | |||||
Marshal.FreeHGlobal(Connection); | |||||
Connection = IntPtr.Zero; | |||||
} | |||||
if (pOptions != IntPtr.Zero) | |||||
{ | |||||
Marshal.FreeHGlobal(pOptions); | |||||
pOptions = IntPtr.Zero; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
public class WinINetSetting | |||||
{ | |||||
public InternetPerConnectionFlags Flags = InternetPerConnectionFlags.Direct; | |||||
public string ProxyServer; | |||||
public string ProxyBypass; | |||||
public string AutoConfigUrl; | |||||
} | |||||
public class WinINet | |||||
{ | |||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||||
private const string SettingFile = "wininet-setting.json"; | |||||
private static WinINetSetting initialSetting; | |||||
public static bool operational { get; private set; } = true; | |||||
static WinINet() | |||||
{ | |||||
try | |||||
{ | |||||
Query(); | |||||
} | |||||
catch (DllNotFoundException) | |||||
{ | |||||
operational = false; | |||||
// Not on windows | |||||
logger.Info("You are not running on Windows platform, system proxy will disable"); | |||||
} | |||||
catch (Win32Exception we) | |||||
{ | |||||
if (we.NativeErrorCode == 12178) | |||||
{ | |||||
logger.Warn("WPAD service is not running, system proxy will disable"); | |||||
// WPAD not running | |||||
} | |||||
else | |||||
{ | |||||
throw we; | |||||
} | |||||
} | |||||
Load(); | |||||
} | |||||
public static void ProxyGlobal(string server, string bypass) | |||||
{ | |||||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||||
{ | |||||
GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.Proxy|InternetPerConnectionFlags.Direct), | |||||
GetOption(InternetPerConnectionOptionEnum.ProxyServer,server), | |||||
GetOption(InternetPerConnectionOptionEnum.ProxyBypass,bypass), | |||||
}; | |||||
Exec(options); | |||||
} | |||||
public static void ProxyPAC(string url) | |||||
{ | |||||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||||
{ | |||||
GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.AutoProxyUrl|InternetPerConnectionFlags.Direct), | |||||
GetOption(InternetPerConnectionOptionEnum.AutoConfigUrl,url), | |||||
}; | |||||
Exec(options); | |||||
} | |||||
public static void Direct() | |||||
{ | |||||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||||
{ | |||||
GetOption(InternetPerConnectionOptionEnum.Flags,InternetPerConnectionFlags.Direct), | |||||
}; | |||||
Exec(options); | |||||
} | |||||
private static void Load() | |||||
{ | |||||
try | |||||
{ | |||||
string configContent = File.ReadAllText(Utils.GetTempPath(SettingFile)); | |||||
initialSetting = JsonConvert.DeserializeObject<WinINetSetting>(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) | |||||
{ | |||||
// logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private static void Record() | |||||
{ | |||||
initialSetting ??= Query(); | |||||
} | |||||
public static void Restore() | |||||
{ | |||||
Set(initialSetting); | |||||
} | |||||
public static void Set(WinINetSetting setting) | |||||
{ | |||||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||||
{ | |||||
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() | |||||
{ | |||||
if (!operational) | |||||
{ | |||||
return new WinINetSetting(); | |||||
} | |||||
List<InternetPerConnectionOption> options = new List<InternetPerConnectionOption> | |||||
{ | |||||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.FlagsUI}, | |||||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.ProxyServer}, | |||||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.ProxyBypass}, | |||||
new InternetPerConnectionOption{dwOption = (int)InternetPerConnectionOptionEnum.AutoConfigUrl}, | |||||
}; | |||||
(IntPtr unmanagedList, int listSize) = PrepareOptionList(options, null); | |||||
bool ok = InternetQueryOption(IntPtr.Zero, (int)InternetOptions.PerConnectionOption, unmanagedList, ref listSize); | |||||
if (!ok) | |||||
{ | |||||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||||
} | |||||
WinINetSetting proxy = new WinINetSetting(); | |||||
InternetPerConnectionOptionList ret = Marshal.PtrToStructure<InternetPerConnectionOptionList>(unmanagedList); | |||||
IntPtr p = ret.pOptions; | |||||
int nOption = ret.OptionCount; | |||||
List<InternetPerConnectionOption> outOptions = new List<InternetPerConnectionOption>(); | |||||
for (int i = 0; i < nOption; i++) | |||||
{ | |||||
InternetPerConnectionOption o = Marshal.PtrToStructure<InternetPerConnectionOption>(p); | |||||
outOptions.Add(o); | |||||
p += Marshal.SizeOf(o); | |||||
} | |||||
foreach (InternetPerConnectionOption 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, | |||||
InternetPerConnectionFlags flag | |||||
) | |||||
{ | |||||
return new InternetPerConnectionOption | |||||
{ | |||||
dwOption = (int)option, | |||||
Value = | |||||
{ | |||||
dwValue = (int)flag, | |||||
} | |||||
}; | |||||
} | |||||
private static InternetPerConnectionOption GetOption( | |||||
InternetPerConnectionOptionEnum option, | |||||
string param | |||||
) | |||||
{ | |||||
return new InternetPerConnectionOption | |||||
{ | |||||
dwOption = (int)option, | |||||
Value = | |||||
{ | |||||
pszValue = Marshal.StringToCoTaskMemAuto(param), | |||||
} | |||||
}; | |||||
} | |||||
private static (IntPtr, int) PrepareOptionList(List<InternetPerConnectionOption> options, string connName) | |||||
{ | |||||
int len = options.Sum(o => Marshal.SizeOf(o)); | |||||
IntPtr buf = Marshal.AllocCoTaskMem(len); | |||||
IntPtr cur = buf; | |||||
foreach (InternetPerConnectionOption o in options) | |||||
{ | |||||
Marshal.StructureToPtr(o, cur, false); | |||||
cur += Marshal.SizeOf(o); | |||||
} | |||||
InternetPerConnectionOptionList optionList = new InternetPerConnectionOptionList | |||||
{ | |||||
pOptions = buf, | |||||
OptionCount = options.Count, | |||||
Connection = string.IsNullOrEmpty(connName) | |||||
? IntPtr.Zero | |||||
: Marshal.StringToHGlobalAuto(connName), | |||||
OptionError = 0, | |||||
}; | |||||
int listSize = Marshal.SizeOf(optionList); | |||||
optionList.Size = listSize; | |||||
IntPtr unmanagedList = Marshal.AllocCoTaskMem(listSize); | |||||
Marshal.StructureToPtr(optionList, unmanagedList, true); | |||||
return (unmanagedList, listSize); | |||||
} | |||||
private static void ClearOptionList(IntPtr list) | |||||
{ | |||||
InternetPerConnectionOptionList l = Marshal.PtrToStructure<InternetPerConnectionOptionList>(list); | |||||
Marshal.FreeCoTaskMem(l.pOptions); | |||||
Marshal.FreeCoTaskMem(list); | |||||
} | |||||
private static void Exec(List<InternetPerConnectionOption> options) | |||||
{ | |||||
// TODO: optimize load and save | |||||
Load(); | |||||
Record(); | |||||
Exec(options, null); | |||||
foreach (string conn in RAS.GetAllConnections()) | |||||
{ | |||||
Exec(options, conn); | |||||
} | |||||
Save(); | |||||
} | |||||
private static void Exec(List<InternetPerConnectionOption> options, string connName) | |||||
{ | |||||
if (!operational) | |||||
{ | |||||
return; | |||||
} | |||||
(IntPtr unmanagedList, int listSize) = PrepareOptionList(options, connName); | |||||
bool ok = InternetSetOption( | |||||
IntPtr.Zero, | |||||
(int)InternetOptions.PerConnectionOption, | |||||
unmanagedList, | |||||
listSize | |||||
); | |||||
if (!ok) | |||||
{ | |||||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||||
} | |||||
ClearOptionList(unmanagedList); | |||||
ok = InternetSetOption( | |||||
IntPtr.Zero, | |||||
(int)InternetOptions.ProxySettingChanged, | |||||
IntPtr.Zero, | |||||
0 | |||||
); | |||||
if (!ok) | |||||
{ | |||||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||||
} | |||||
ok = InternetSetOption( | |||||
IntPtr.Zero, | |||||
(int)InternetOptions.Refresh, | |||||
IntPtr.Zero, | |||||
0 | |||||
); | |||||
if (!ok) | |||||
{ | |||||
throw new Win32Exception(Marshal.GetLastWin32Error()); | |||||
} | |||||
} | |||||
[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)] | |||||
private static extern bool InternetQueryOption(IntPtr hInternet, uint dwOption, IntPtr lpBuffer, ref int lpdwBufferLength); | |||||
} | |||||
} |
@@ -3,6 +3,7 @@ using Shadowsocks.Controller; | |||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
using Shadowsocks.Properties; | using Shadowsocks.Properties; | ||||
using Shadowsocks.Util; | using Shadowsocks.Util; | ||||
using Shadowsocks.Util.SystemProxy; | |||||
using System; | using System; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Drawing; | using System.Drawing; | ||||
@@ -38,7 +39,7 @@ namespace Shadowsocks.View | |||||
private ToolStripMenuItem disableItem; | private ToolStripMenuItem disableItem; | ||||
private ToolStripMenuItem AutoStartupItem; | private ToolStripMenuItem AutoStartupItem; | ||||
private ToolStripMenuItem ShareOverLANItem; | private ToolStripMenuItem ShareOverLANItem; | ||||
private ToolStripSeparator SeperatorItem; | |||||
private ToolStripSeparator SeperatorItem; | |||||
private ToolStripMenuItem ConfigItem; | private ToolStripMenuItem ConfigItem; | ||||
private ToolStripMenuItem ProtocolHandlerItem; | private ToolStripMenuItem ProtocolHandlerItem; | ||||
private ToolStripMenuItem ServersItem; | private ToolStripMenuItem ServersItem; | ||||
@@ -507,7 +508,8 @@ namespace Shadowsocks.View | |||||
) | ) | ||||
{ | { | ||||
menuItem.Checked = true; | menuItem.Checked = true; | ||||
} else | |||||
} | |||||
else | |||||
{ | { | ||||
menuItem.Checked = false; | menuItem.Checked = false; | ||||
} | } | ||||
@@ -671,6 +673,9 @@ namespace Shadowsocks.View | |||||
globalModeItem.Checked = config.global; | globalModeItem.Checked = config.global; | ||||
PACModeItem.Checked = !config.global; | PACModeItem.Checked = !config.global; | ||||
} | } | ||||
globalModeItem.Enabled = WinINet.operational; | |||||
PACModeItem.Enabled = WinINet.operational; | |||||
} | } | ||||
private void GlobalModeItem_Click(object sender, EventArgs e) | private void GlobalModeItem_Click(object sender, EventArgs e) | ||||
@@ -31,8 +31,6 @@ | |||||
<None Remove="Data\NLog.config" /> | <None Remove="Data\NLog.config" /> | ||||
<None Remove="Data\privoxy.exe.gz" /> | <None Remove="Data\privoxy.exe.gz" /> | ||||
<None Remove="Data\privoxy_conf.txt" /> | <None Remove="Data\privoxy_conf.txt" /> | ||||
<None Remove="Data\sysproxy.exe.gz" /> | |||||
<None Remove="Data\sysproxy64.exe.gz" /> | |||||
<None Remove="Data\user-rule.txt" /> | <None Remove="Data\user-rule.txt" /> | ||||
<None Remove="Resources\ss128.pdn" /> | <None Remove="Resources\ss128.pdn" /> | ||||
<None Remove="Resources\ss32.pdn" /> | <None Remove="Resources\ss32.pdn" /> | ||||
@@ -95,8 +93,6 @@ | |||||
<Resource Include="Data\NLog.config" /> | <Resource Include="Data\NLog.config" /> | ||||
<Resource Include="Data\privoxy.exe.gz" /> | <Resource Include="Data\privoxy.exe.gz" /> | ||||
<Resource Include="Data\privoxy_conf.txt" /> | <Resource Include="Data\privoxy_conf.txt" /> | ||||
<Resource Include="Data\sysproxy.exe.gz" /> | |||||
<Resource Include="Data\sysproxy64.exe.gz" /> | |||||
<Resource Include="Data\user-rule.txt" /> | <Resource Include="Data\user-rule.txt" /> | ||||
<Resource Include="Resources\ss128.pdn" /> | <Resource Include="Resources\ss128.pdn" /> | ||||
<Resource Include="Resources\ss32.pdn" /> | <Resource Include="Resources\ss32.pdn" /> | ||||