- Split the file operations to new PACDaemon - More precise matching logic for handling PAC content http requesttags/4.1.8.0
@@ -48,24 +48,24 @@ namespace Shadowsocks.Controller | |||||
public static bool MergeAndWritePACFile(string gfwListResult) | public static bool MergeAndWritePACFile(string gfwListResult) | ||||
{ | { | ||||
string abpContent = MergePACFile(gfwListResult); | string abpContent = MergePACFile(gfwListResult); | ||||
if (File.Exists(PACServer.PAC_FILE)) | |||||
if (File.Exists(PACDaemon.PAC_FILE)) | |||||
{ | { | ||||
string original = FileManager.NonExclusiveReadAllText(PACServer.PAC_FILE, Encoding.UTF8); | |||||
string original = FileManager.NonExclusiveReadAllText(PACDaemon.PAC_FILE, Encoding.UTF8); | |||||
if (original == abpContent) | if (original == abpContent) | ||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); | |||||
File.WriteAllText(PACDaemon.PAC_FILE, abpContent, Encoding.UTF8); | |||||
return true; | return true; | ||||
} | } | ||||
private static string MergePACFile(string gfwListResult) | private static string MergePACFile(string gfwListResult) | ||||
{ | { | ||||
string abpContent; | string abpContent; | ||||
if (File.Exists(PACServer.USER_ABP_FILE)) | |||||
if (File.Exists(PACDaemon.USER_ABP_FILE)) | |||||
{ | { | ||||
abpContent = FileManager.NonExclusiveReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8); | |||||
abpContent = FileManager.NonExclusiveReadAllText(PACDaemon.USER_ABP_FILE, Encoding.UTF8); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -73,9 +73,9 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
List<string> userruleLines = new List<string>(); | List<string> userruleLines = new List<string>(); | ||||
if (File.Exists(PACServer.USER_RULE_FILE)) | |||||
if (File.Exists(PACDaemon.USER_RULE_FILE)) | |||||
{ | { | ||||
string userrulesString = FileManager.NonExclusiveReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8); | |||||
string userrulesString = FileManager.NonExclusiveReadAllText(PACDaemon.USER_RULE_FILE, Encoding.UTF8); | |||||
userruleLines = ParseToValidList(userrulesString); | userruleLines = ParseToValidList(userrulesString); | ||||
} | } | ||||
@@ -0,0 +1,135 @@ | |||||
using Shadowsocks.Properties; | |||||
using Shadowsocks.Util; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Shadowsocks.Controller | |||||
{ | |||||
/// <summary> | |||||
/// Processing the PAC file content | |||||
/// </summary> | |||||
public class PACDaemon | |||||
{ | |||||
public const string PAC_FILE = "pac.txt"; | |||||
public const string USER_RULE_FILE = "user-rule.txt"; | |||||
public const string USER_ABP_FILE = "abp.txt"; | |||||
FileSystemWatcher PACFileWatcher; | |||||
FileSystemWatcher UserRuleFileWatcher; | |||||
public event EventHandler PACFileChanged; | |||||
public event EventHandler UserRuleFileChanged; | |||||
public PACDaemon() | |||||
{ | |||||
this.WatchPacFile(); | |||||
this.WatchUserRuleFile(); | |||||
} | |||||
public string TouchPACFile() | |||||
{ | |||||
if (File.Exists(PAC_FILE)) | |||||
{ | |||||
return PAC_FILE; | |||||
} | |||||
else | |||||
{ | |||||
FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt); | |||||
return PAC_FILE; | |||||
} | |||||
} | |||||
internal string TouchUserRuleFile() | |||||
{ | |||||
if (File.Exists(USER_RULE_FILE)) | |||||
{ | |||||
return USER_RULE_FILE; | |||||
} | |||||
else | |||||
{ | |||||
File.WriteAllText(USER_RULE_FILE, Resources.user_rule); | |||||
return USER_RULE_FILE; | |||||
} | |||||
} | |||||
internal string GetPACContent() | |||||
{ | |||||
if (File.Exists(PAC_FILE)) | |||||
{ | |||||
return File.ReadAllText(PAC_FILE, Encoding.UTF8); | |||||
} | |||||
else | |||||
{ | |||||
return Utils.UnGzip(Resources.proxy_pac_txt); | |||||
} | |||||
} | |||||
private void WatchPacFile() | |||||
{ | |||||
PACFileWatcher?.Dispose(); | |||||
PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||||
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||||
PACFileWatcher.Filter = PAC_FILE; | |||||
PACFileWatcher.Changed += PACFileWatcher_Changed; | |||||
PACFileWatcher.Created += PACFileWatcher_Changed; | |||||
PACFileWatcher.Deleted += PACFileWatcher_Changed; | |||||
PACFileWatcher.Renamed += PACFileWatcher_Changed; | |||||
PACFileWatcher.EnableRaisingEvents = true; | |||||
} | |||||
private void WatchUserRuleFile() | |||||
{ | |||||
UserRuleFileWatcher?.Dispose(); | |||||
UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||||
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||||
UserRuleFileWatcher.Filter = USER_RULE_FILE; | |||||
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.EnableRaisingEvents = true; | |||||
} | |||||
#region FileSystemWatcher.OnChanged() | |||||
// FileSystemWatcher Changed event is raised twice | |||||
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice | |||||
// Add a short delay to avoid raise event twice in a short period | |||||
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) | |||||
{ | |||||
if (PACFileChanged != null) | |||||
{ | |||||
Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
Task.Factory.StartNew(() => | |||||
{ | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = false; | |||||
System.Threading.Thread.Sleep(10); | |||||
PACFileChanged(this, new EventArgs()); | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = true; | |||||
}); | |||||
} | |||||
} | |||||
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) | |||||
{ | |||||
if (UserRuleFileChanged != null) | |||||
{ | |||||
Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
Task.Factory.StartNew(() => | |||||
{ | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = false; | |||||
System.Threading.Thread.Sleep(10); | |||||
UserRuleFileChanged(this, new EventArgs()); | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = true; | |||||
}); | |||||
} | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -15,28 +15,21 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
public class PACServer : Listener.Service | public class PACServer : Listener.Service | ||||
{ | { | ||||
public const string PAC_FILE = "pac.txt"; | |||||
public const string USER_RULE_FILE = "user-rule.txt"; | |||||
public const string USER_ABP_FILE = "abp.txt"; | |||||
public const string RESOURCE_NAME = "pac"; | |||||
private string PacSecret { get; set; } = ""; | private string PacSecret { get; set; } = ""; | ||||
public string PacUrl { get; private set; } = ""; | public string PacUrl { get; private set; } = ""; | ||||
FileSystemWatcher PACFileWatcher; | |||||
FileSystemWatcher UserRuleFileWatcher; | |||||
private Configuration _config; | private Configuration _config; | ||||
private PACDaemon _pacDaemon; | |||||
public event EventHandler PACFileChanged; | |||||
public event EventHandler UserRuleFileChanged; | |||||
public PACServer() | |||||
public PACServer(PACDaemon pacDaemon) | |||||
{ | { | ||||
this.WatchPacFile(); | |||||
this.WatchUserRuleFile(); | |||||
_pacDaemon = pacDaemon; | |||||
} | } | ||||
public void UpdateConfiguration(Configuration config) | |||||
public void UpdatePACURL(Configuration config) | |||||
{ | { | ||||
this._config = config; | this._config = config; | ||||
@@ -51,7 +44,7 @@ namespace Shadowsocks.Controller | |||||
PacSecret = ""; | PacSecret = ""; | ||||
} | } | ||||
PacUrl = $"http://{config.localHost}:{config.localPort}/pac?t={GetTimestamp(DateTime.Now)}{PacSecret}"; | |||||
PacUrl = $"http://{config.localHost}:{config.localPort}/{RESOURCE_NAME}?t={GetTimestamp(DateTime.Now)}{PacSecret}"; | |||||
} | } | ||||
@@ -66,15 +59,61 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
try | try | ||||
{ | { | ||||
/* | |||||
* RFC 7230 | |||||
* | |||||
GET /hello.txt HTTP/1.1 | |||||
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 | |||||
Host: www.example.com | |||||
Accept-Language: en, mi | |||||
*/ | |||||
string request = Encoding.UTF8.GetString(firstPacket, 0, length); | string request = Encoding.UTF8.GetString(firstPacket, 0, length); | ||||
string[] lines = request.Split('\r', '\n'); | string[] lines = request.Split('\r', '\n'); | ||||
bool hostMatch = false, pathMatch = false, useSocks = false; | bool hostMatch = false, pathMatch = false, useSocks = false; | ||||
bool secretMatch = PacSecret.IsNullOrEmpty(); | bool secretMatch = PacSecret.IsNullOrEmpty(); | ||||
foreach (string line in lines) | |||||
if (lines.Length < 2) // need at lease RequestLine + Host | |||||
{ | { | ||||
string[] kv = line.Split(new char[] { ':' }, 2); | |||||
return false; | |||||
} | |||||
// parse request line | |||||
string requestLine = lines[0]; | |||||
// GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1 | |||||
string[] requestItems = requestLine.Split(' '); | |||||
if (requestItems.Length == 3 && requestItems[0] == "GET") | |||||
{ | |||||
int index = requestItems[1].IndexOf('?'); | |||||
if (index < 0) | |||||
{ | |||||
index = requestItems[1].Length; | |||||
} | |||||
string resourceString = requestItems[1].Substring(0, index).Remove(0, 1); | |||||
if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase)) | |||||
{ | |||||
pathMatch = true; | |||||
if (!secretMatch) | |||||
{ | |||||
string queryString = requestItems[1].Substring(index); | |||||
if (queryString.Contains(PacSecret)) | |||||
{ | |||||
secretMatch = true; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// parse request header | |||||
for (int i = 1; i < lines.Length; i++) | |||||
{ | |||||
if (string.IsNullOrEmpty(lines[i])) | |||||
continue; | |||||
string[] kv = lines[i].Split(new char[] { ':' }, 2); | |||||
if (kv.Length == 2) | if (kv.Length == 2) | ||||
{ | { | ||||
if (kv[0] == "Host") | if (kv[0] == "Host") | ||||
@@ -93,21 +132,8 @@ namespace Shadowsocks.Controller | |||||
// } | // } | ||||
//} | //} | ||||
} | } | ||||
else if (kv.Length == 1) | |||||
{ | |||||
if (line.IndexOf("pac", StringComparison.Ordinal) >= 0) | |||||
{ | |||||
pathMatch = true; | |||||
} | |||||
if (!secretMatch) | |||||
{ | |||||
if (line.IndexOf(PacSecret, StringComparison.Ordinal) >= 0) | |||||
{ | |||||
secretMatch = true; | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
if (hostMatch && pathMatch) | if (hostMatch && pathMatch) | ||||
{ | { | ||||
if (!secretMatch) | if (!secretMatch) | ||||
@@ -128,43 +154,7 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
} | } | ||||
public string TouchPACFile() | |||||
{ | |||||
if (File.Exists(PAC_FILE)) | |||||
{ | |||||
return PAC_FILE; | |||||
} | |||||
else | |||||
{ | |||||
FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt); | |||||
return PAC_FILE; | |||||
} | |||||
} | |||||
internal string TouchUserRuleFile() | |||||
{ | |||||
if (File.Exists(USER_RULE_FILE)) | |||||
{ | |||||
return USER_RULE_FILE; | |||||
} | |||||
else | |||||
{ | |||||
File.WriteAllText(USER_RULE_FILE, Resources.user_rule); | |||||
return USER_RULE_FILE; | |||||
} | |||||
} | |||||
private string GetPACContent() | |||||
{ | |||||
if (File.Exists(PAC_FILE)) | |||||
{ | |||||
return File.ReadAllText(PAC_FILE, Encoding.UTF8); | |||||
} | |||||
else | |||||
{ | |||||
return Utils.UnGzip(Resources.proxy_pac_txt); | |||||
} | |||||
} | |||||
public void SendResponse(Socket socket, bool useSocks) | public void SendResponse(Socket socket, bool useSocks) | ||||
{ | { | ||||
@@ -174,7 +164,7 @@ namespace Shadowsocks.Controller | |||||
string proxy = GetPACAddress(localEndPoint, useSocks); | string proxy = GetPACAddress(localEndPoint, useSocks); | ||||
string pacContent = GetPACContent().Replace("__PROXY__", proxy); | |||||
string pacContent = _pacDaemon.GetPACContent().Replace("__PROXY__", proxy); | |||||
string responseHead = String.Format(@"HTTP/1.1 200 OK | string responseHead = String.Format(@"HTTP/1.1 200 OK | ||||
Server: Shadowsocks | Server: Shadowsocks | ||||
@@ -205,66 +195,6 @@ Connection: Close | |||||
{ } | { } | ||||
} | } | ||||
private void WatchPacFile() | |||||
{ | |||||
PACFileWatcher?.Dispose(); | |||||
PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||||
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||||
PACFileWatcher.Filter = PAC_FILE; | |||||
PACFileWatcher.Changed += PACFileWatcher_Changed; | |||||
PACFileWatcher.Created += PACFileWatcher_Changed; | |||||
PACFileWatcher.Deleted += PACFileWatcher_Changed; | |||||
PACFileWatcher.Renamed += PACFileWatcher_Changed; | |||||
PACFileWatcher.EnableRaisingEvents = true; | |||||
} | |||||
private void WatchUserRuleFile() | |||||
{ | |||||
UserRuleFileWatcher?.Dispose(); | |||||
UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||||
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||||
UserRuleFileWatcher.Filter = USER_RULE_FILE; | |||||
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; | |||||
UserRuleFileWatcher.EnableRaisingEvents = true; | |||||
} | |||||
#region FileSystemWatcher.OnChanged() | |||||
// FileSystemWatcher Changed event is raised twice | |||||
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice | |||||
// Add a short delay to avoid raise event twice in a short period | |||||
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) | |||||
{ | |||||
if (PACFileChanged != null) | |||||
{ | |||||
Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
Task.Factory.StartNew(() => | |||||
{ | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = false; | |||||
System.Threading.Thread.Sleep(10); | |||||
PACFileChanged(this, new EventArgs()); | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = true; | |||||
}); | |||||
} | |||||
} | |||||
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) | |||||
{ | |||||
if (UserRuleFileChanged != null) | |||||
{ | |||||
Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
Task.Factory.StartNew(() => | |||||
{ | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = false; | |||||
System.Threading.Thread.Sleep(10); | |||||
UserRuleFileChanged(this, new EventArgs()); | |||||
((FileSystemWatcher)sender).EnableRaisingEvents = true; | |||||
}); | |||||
} | |||||
} | |||||
#endregion | |||||
private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) | private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) | ||||
{ | { | ||||
@@ -28,6 +28,7 @@ namespace Shadowsocks.Controller | |||||
private Thread _trafficThread; | private Thread _trafficThread; | ||||
private Listener _listener; | private Listener _listener; | ||||
private PACDaemon _pacDaemon; | |||||
private PACServer _pacServer; | private PACServer _pacServer; | ||||
private Configuration _config; | private Configuration _config; | ||||
private StrategyManager _strategyManager; | private StrategyManager _strategyManager; | ||||
@@ -299,14 +300,14 @@ namespace Shadowsocks.Controller | |||||
public void TouchPACFile() | public void TouchPACFile() | ||||
{ | { | ||||
string pacFilename = _pacServer.TouchPACFile(); | |||||
string pacFilename = _pacDaemon.TouchPACFile(); | |||||
PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename }); | PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename }); | ||||
} | } | ||||
public void TouchUserRuleFile() | public void TouchUserRuleFile() | ||||
{ | { | ||||
string userRuleFilename = _pacServer.TouchUserRuleFile(); | |||||
string userRuleFilename = _pacDaemon.TouchUserRuleFile(); | |||||
UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename }); | UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename }); | ||||
} | } | ||||
@@ -468,13 +469,20 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
privoxyRunner = new PrivoxyRunner(); | privoxyRunner = new PrivoxyRunner(); | ||||
} | } | ||||
if (_pacDaemon == null) | |||||
{ | |||||
_pacDaemon = new PACDaemon(); | |||||
_pacDaemon.PACFileChanged += PacDaemon_PACFileChanged; | |||||
_pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged; | |||||
} | |||||
if (_pacServer == null) | if (_pacServer == null) | ||||
{ | { | ||||
_pacServer = new PACServer(); | |||||
_pacServer.PACFileChanged += PacServer_PACFileChanged; | |||||
_pacServer.UserRuleFileChanged += PacServer_UserRuleFileChanged; | |||||
_pacServer = new PACServer(_pacDaemon); | |||||
} | } | ||||
_pacServer.UpdateConfiguration(_config); | |||||
_pacServer.UpdatePACURL(_config); | |||||
if (gfwListUpdater == null) | if (gfwListUpdater == null) | ||||
{ | { | ||||
gfwListUpdater = new GFWListUpdater(); | gfwListUpdater = new GFWListUpdater(); | ||||
@@ -561,7 +569,7 @@ namespace Shadowsocks.Controller | |||||
SystemProxy.Update(_config, false, _pacServer); | SystemProxy.Update(_config, false, _pacServer); | ||||
} | } | ||||
private void PacServer_PACFileChanged(object sender, EventArgs e) | |||||
private void PacDaemon_PACFileChanged(object sender, EventArgs e) | |||||
{ | { | ||||
UpdateSystemProxy(); | UpdateSystemProxy(); | ||||
} | } | ||||
@@ -577,7 +585,7 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' }; | private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' }; | ||||
private void PacServer_UserRuleFileChanged(object sender, EventArgs e) | |||||
private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e) | |||||
{ | { | ||||
if (!File.Exists(Utils.GetTempPath("gfwlist.txt"))) | if (!File.Exists(Utils.GetTempPath("gfwlist.txt"))) | ||||
{ | { | ||||
@@ -103,6 +103,7 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Compile Include="Controller\HotkeyReg.cs" /> | <Compile Include="Controller\HotkeyReg.cs" /> | ||||
<Compile Include="Controller\Service\PACDaemon.cs" /> | |||||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | <Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | ||||
<Compile Include="Encryption\AEAD\AEADEncryptor.cs" /> | <Compile Include="Encryption\AEAD\AEADEncryptor.cs" /> | ||||
<Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" /> | <Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" /> | ||||