@@ -1,17 +1,13 @@ | |||||
using NLog; | |||||
using Shadowsocks.Properties; | |||||
using Shadowsocks.Util; | |||||
using Splat; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO; | using System.IO; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
using Newtonsoft.Json; | |||||
using Shadowsocks.Model; | |||||
using System.Net; | |||||
using System.Net.Http; | using System.Net.Http; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Security.Cryptography; | using System.Security.Cryptography; | ||||
using System.Text.Json; | |||||
namespace Shadowsocks.PAC | namespace Shadowsocks.PAC | ||||
{ | { | ||||
@@ -25,32 +21,31 @@ namespace Shadowsocks.PAC | |||||
} | } | ||||
} | } | ||||
public static class GeositeUpdater | |||||
public class GeositeUpdater : IEnableLogger | |||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public event EventHandler<GeositeResultEventArgs> UpdateCompleted; | |||||
public static event EventHandler<GeositeResultEventArgs> UpdateCompleted; | |||||
public event ErrorEventHandler Error; | |||||
public static event ErrorEventHandler Error; | |||||
private readonly string DATABASE_PATH; | |||||
private static readonly string DATABASE_PATH = Utils.GetTempPath("dlc.dat"); | |||||
private readonly string GEOSITE_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat"; | |||||
private readonly string GEOSITE_SHA256SUM_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat.sha256sum"; | |||||
private byte[] geositeDB; | |||||
private static readonly string GEOSITE_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat"; | |||||
private static readonly string GEOSITE_SHA256SUM_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat.sha256sum"; | |||||
private static byte[] geositeDB; | |||||
public readonly Dictionary<string, IList<DomainObject>> Geosites = new Dictionary<string, IList<DomainObject>>(); | |||||
public static readonly Dictionary<string, IList<DomainObject>> Geosites = new Dictionary<string, IList<DomainObject>>(); | |||||
static GeositeUpdater() | |||||
public GeositeUpdater(string dlcPath) | |||||
{ | { | ||||
DATABASE_PATH = dlcPath; | |||||
if (File.Exists(DATABASE_PATH) && new FileInfo(DATABASE_PATH).Length > 0) | if (File.Exists(DATABASE_PATH) && new FileInfo(DATABASE_PATH).Length > 0) | ||||
{ | { | ||||
geositeDB = File.ReadAllBytes(DATABASE_PATH); | geositeDB = File.ReadAllBytes(DATABASE_PATH); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
geositeDB = Resources.dlc_dat; | |||||
File.WriteAllBytes(DATABASE_PATH, Resources.dlc_dat); | |||||
geositeDB = Properties.Resources.dlc; | |||||
File.WriteAllBytes(DATABASE_PATH, Properties.Resources.dlc); | |||||
} | } | ||||
LoadGeositeList(); | LoadGeositeList(); | ||||
} | } | ||||
@@ -58,7 +53,7 @@ namespace Shadowsocks.PAC | |||||
/// <summary> | /// <summary> | ||||
/// load new GeoSite data from geositeDB | /// load new GeoSite data from geositeDB | ||||
/// </summary> | /// </summary> | ||||
static void LoadGeositeList() | |||||
private void LoadGeositeList() | |||||
{ | { | ||||
var list = GeositeList.Parser.ParseFrom(geositeDB); | var list = GeositeList.Parser.ParseFrom(geositeDB); | ||||
foreach (var item in list.Entries) | foreach (var item in list.Entries) | ||||
@@ -67,42 +62,41 @@ namespace Shadowsocks.PAC | |||||
} | } | ||||
} | } | ||||
public static void ResetEvent() | |||||
public void ResetEvent() | |||||
{ | { | ||||
UpdateCompleted = null; | UpdateCompleted = null; | ||||
Error = null; | Error = null; | ||||
} | } | ||||
public static async Task UpdatePACFromGeosite() | |||||
public async Task UpdatePACFromGeosite(PACSettings pACSettings) | |||||
{ | { | ||||
string geositeUrl = GEOSITE_URL; | string geositeUrl = GEOSITE_URL; | ||||
string geositeSha256sumUrl = GEOSITE_SHA256SUM_URL; | string geositeSha256sumUrl = GEOSITE_SHA256SUM_URL; | ||||
SHA256 mySHA256 = SHA256.Create(); | SHA256 mySHA256 = SHA256.Create(); | ||||
var config = Program.MainController.GetCurrentConfiguration(); | |||||
bool blacklist = config.geositePreferDirect; | |||||
var httpClient = Program.MainController.GetHttpClient(); | |||||
bool blacklist = pACSettings.PACDefaultToDirect; | |||||
var httpClient = Locator.Current.GetService<HttpClient>(); | |||||
if (!string.IsNullOrWhiteSpace(config.geositeUrl)) | |||||
if (!string.IsNullOrWhiteSpace(pACSettings.CustomGeositeUrl)) | |||||
{ | { | ||||
logger.Info("Found custom Geosite URL in config file"); | |||||
geositeUrl = config.geositeUrl; | |||||
this.Log().Info("Found custom Geosite URL in config file"); | |||||
geositeUrl = pACSettings.CustomGeositeUrl; | |||||
} | } | ||||
logger.Info($"Checking Geosite from {geositeUrl}"); | |||||
this.Log().Info($"Checking Geosite from {geositeUrl}"); | |||||
try | try | ||||
{ | { | ||||
// download checksum first | // download checksum first | ||||
var geositeSha256sum = await httpClient.GetStringAsync(geositeSha256sumUrl); | var geositeSha256sum = await httpClient.GetStringAsync(geositeSha256sumUrl); | ||||
geositeSha256sum = geositeSha256sum.Substring(0, 64).ToUpper(); | geositeSha256sum = geositeSha256sum.Substring(0, 64).ToUpper(); | ||||
logger.Info($"Got Sha256sum: {geositeSha256sum}"); | |||||
this.Log().Info($"Got Sha256sum: {geositeSha256sum}"); | |||||
// compare downloaded checksum with local geositeDB | // compare downloaded checksum with local geositeDB | ||||
byte[] localDBHashBytes = mySHA256.ComputeHash(geositeDB); | byte[] localDBHashBytes = mySHA256.ComputeHash(geositeDB); | ||||
string localDBHash = BitConverter.ToString(localDBHashBytes).Replace("-", String.Empty); | string localDBHash = BitConverter.ToString(localDBHashBytes).Replace("-", String.Empty); | ||||
logger.Info($"Local Sha256sum: {localDBHash}"); | |||||
this.Log().Info($"Local Sha256sum: {localDBHash}"); | |||||
// if already latest | // if already latest | ||||
if (geositeSha256sum == localDBHash) | if (geositeSha256sum == localDBHash) | ||||
{ | { | ||||
logger.Info("Local GeoSite DB is up to date."); | |||||
this.Log().Info("Local GeoSite DB is up to date."); | |||||
return; | return; | ||||
} | } | ||||
@@ -112,15 +106,15 @@ namespace Shadowsocks.PAC | |||||
// verify sha256sum | // verify sha256sum | ||||
byte[] downloadedDBHashBytes = mySHA256.ComputeHash(downloadedBytes); | byte[] downloadedDBHashBytes = mySHA256.ComputeHash(downloadedBytes); | ||||
string downloadedDBHash = BitConverter.ToString(downloadedDBHashBytes).Replace("-", String.Empty); | string downloadedDBHash = BitConverter.ToString(downloadedDBHashBytes).Replace("-", String.Empty); | ||||
logger.Info($"Actual Sha256sum: {downloadedDBHash}"); | |||||
this.Log().Info($"Actual Sha256sum: {downloadedDBHash}"); | |||||
if (geositeSha256sum != downloadedDBHash) | if (geositeSha256sum != downloadedDBHash) | ||||
{ | { | ||||
logger.Info("Sha256sum Verification: FAILED. Downloaded GeoSite DB is corrupted. Aborting the update."); | |||||
this.Log().Info("Sha256sum Verification: FAILED. Downloaded GeoSite DB is corrupted. Aborting the update."); | |||||
throw new Exception("Sha256sum mismatch"); | throw new Exception("Sha256sum mismatch"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
logger.Info("Sha256sum Verification: PASSED. Applying to local GeoSite DB."); | |||||
this.Log().Info("Sha256sum Verification: PASSED. Applying to local GeoSite DB."); | |||||
} | } | ||||
// write to geosite file | // write to geosite file | ||||
@@ -130,7 +124,7 @@ namespace Shadowsocks.PAC | |||||
// update stuff | // update stuff | ||||
geositeDB = downloadedBytes; | geositeDB = downloadedBytes; | ||||
LoadGeositeList(); | LoadGeositeList(); | ||||
bool pacFileChanged = MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, blacklist); | |||||
bool pacFileChanged = MergeAndWritePACFile(pACSettings.GeositeDirectGroups, pACSettings.GeositeProxiedGroups, blacklist); | |||||
UpdateCompleted?.Invoke(null, new GeositeResultEventArgs(pacFileChanged)); | UpdateCompleted?.Invoke(null, new GeositeResultEventArgs(pacFileChanged)); | ||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
@@ -147,12 +141,12 @@ namespace Shadowsocks.PAC | |||||
/// <param name="proxiedGroups">A list of geosite groups configured for proxied connection.</param> | /// <param name="proxiedGroups">A list of geosite groups configured for proxied connection.</param> | ||||
/// <param name="blacklist">Whether to use blacklist mode. False for whitelist.</param> | /// <param name="blacklist">Whether to use blacklist mode. False for whitelist.</param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
public static bool MergeAndWritePACFile(List<string> directGroups, List<string> proxiedGroups, bool blacklist) | |||||
public bool MergeAndWritePACFile(List<string> directGroups, List<string> proxiedGroups, bool blacklist) | |||||
{ | { | ||||
string abpContent = MergePACFile(directGroups, proxiedGroups, blacklist); | string abpContent = MergePACFile(directGroups, proxiedGroups, blacklist); | ||||
if (File.Exists(PACDaemon.PAC_FILE)) | if (File.Exists(PACDaemon.PAC_FILE)) | ||||
{ | { | ||||
string original = FileManager.NonExclusiveReadAllText(PACDaemon.PAC_FILE, Encoding.UTF8); | |||||
string original = File.ReadAllText(PACDaemon.PAC_FILE); | |||||
if (original == abpContent) | if (original == abpContent) | ||||
{ | { | ||||
return false; | return false; | ||||
@@ -167,7 +161,7 @@ namespace Shadowsocks.PAC | |||||
/// </summary> | /// </summary> | ||||
/// <param name="group">The group name to check for.</param> | /// <param name="group">The group name to check for.</param> | ||||
/// <returns>True if the group exists. False if the group doesn't exist.</returns> | /// <returns>True if the group exists. False if the group doesn't exist.</returns> | ||||
public static bool CheckGeositeGroup(string group) => SeparateAttributeFromGroupName(group, out string groupName, out _) && Geosites.ContainsKey(groupName); | |||||
public bool CheckGeositeGroup(string group) => SeparateAttributeFromGroupName(group, out string groupName, out _) && Geosites.ContainsKey(groupName); | |||||
/// <summary> | /// <summary> | ||||
/// Separates the attribute (e.g. @cn) from a group name. | /// Separates the attribute (e.g. @cn) from a group name. | ||||
@@ -177,7 +171,7 @@ namespace Shadowsocks.PAC | |||||
/// <param name="groupName">The group name with the attribute stripped.</param> | /// <param name="groupName">The group name with the attribute stripped.</param> | ||||
/// <param name="attribute">The attribute.</param> | /// <param name="attribute">The attribute.</param> | ||||
/// <returns>True for success. False for more than one '@'.</returns> | /// <returns>True for success. False for more than one '@'.</returns> | ||||
private static bool SeparateAttributeFromGroupName(string group, out string groupName, out string attribute) | |||||
private bool SeparateAttributeFromGroupName(string group, out string groupName, out string attribute) | |||||
{ | { | ||||
var splitGroupAttributeList = group.Split('@'); | var splitGroupAttributeList = group.Split('@'); | ||||
if (splitGroupAttributeList.Length == 1) // no attribute | if (splitGroupAttributeList.Length == 1) // no attribute | ||||
@@ -199,34 +193,39 @@ namespace Shadowsocks.PAC | |||||
return true; | return true; | ||||
} | } | ||||
private static string MergePACFile(List<string> directGroups, List<string> proxiedGroups, bool blacklist) | |||||
private string MergePACFile(List<string> directGroups, List<string> proxiedGroups, bool blacklist) | |||||
{ | { | ||||
string abpContent; | string abpContent; | ||||
if (File.Exists(PACDaemon.USER_ABP_FILE)) | if (File.Exists(PACDaemon.USER_ABP_FILE)) | ||||
{ | { | ||||
abpContent = FileManager.NonExclusiveReadAllText(PACDaemon.USER_ABP_FILE, Encoding.UTF8); | |||||
abpContent = File.ReadAllText(PACDaemon.USER_ABP_FILE); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
abpContent = Resources.abp_js; | |||||
abpContent = Properties.Resources.abp; | |||||
} | } | ||||
List<string> userruleLines = new List<string>(); | List<string> userruleLines = new List<string>(); | ||||
if (File.Exists(PACDaemon.USER_RULE_FILE)) | if (File.Exists(PACDaemon.USER_RULE_FILE)) | ||||
{ | { | ||||
string userrulesString = FileManager.NonExclusiveReadAllText(PACDaemon.USER_RULE_FILE, Encoding.UTF8); | |||||
string userrulesString = File.ReadAllText(PACDaemon.USER_RULE_FILE); | |||||
userruleLines = ProcessUserRules(userrulesString); | userruleLines = ProcessUserRules(userrulesString); | ||||
} | } | ||||
List<string> ruleLines = GenerateRules(directGroups, proxiedGroups, blacklist); | List<string> ruleLines = GenerateRules(directGroups, proxiedGroups, blacklist); | ||||
var jsonSerializerOptions = new JsonSerializerOptions() | |||||
{ | |||||
WriteIndented = true, | |||||
}; | |||||
abpContent = | abpContent = | ||||
$@"var __USERRULES__ = {JsonConvert.SerializeObject(userruleLines, Formatting.Indented)}; | |||||
var __RULES__ = {JsonConvert.SerializeObject(ruleLines, Formatting.Indented)}; | |||||
$@"var __USERRULES__ = {JsonSerializer.Serialize(userruleLines, jsonSerializerOptions)}; | |||||
var __RULES__ = {JsonSerializer.Serialize(ruleLines, jsonSerializerOptions)}; | |||||
{abpContent}"; | {abpContent}"; | ||||
return abpContent; | return abpContent; | ||||
} | } | ||||
private static List<string> ProcessUserRules(string content) | |||||
private List<string> ProcessUserRules(string content) | |||||
{ | { | ||||
List<string> valid_lines = new List<string>(); | List<string> valid_lines = new List<string>(); | ||||
using (var stringReader = new StringReader(content)) | using (var stringReader = new StringReader(content)) | ||||
@@ -248,7 +247,7 @@ var __RULES__ = {JsonConvert.SerializeObject(ruleLines, Formatting.Indented)}; | |||||
/// <param name="proxiedGroups">A list of geosite groups configured for proxied connection.</param> | /// <param name="proxiedGroups">A list of geosite groups configured for proxied connection.</param> | ||||
/// <param name="blacklist">Whether to use blacklist mode. False for whitelist.</param> | /// <param name="blacklist">Whether to use blacklist mode. False for whitelist.</param> | ||||
/// <returns>A list of rule lines.</returns> | /// <returns>A list of rule lines.</returns> | ||||
private static List<string> GenerateRules(List<string> directGroups, List<string> proxiedGroups, bool blacklist) | |||||
private List<string> GenerateRules(List<string> directGroups, List<string> proxiedGroups, bool blacklist) | |||||
{ | { | ||||
List<string> ruleLines; | List<string> ruleLines; | ||||
if (blacklist) // blocking + exception rules | if (blacklist) // blocking + exception rules | ||||
@@ -272,7 +271,7 @@ var __RULES__ = {JsonConvert.SerializeObject(ruleLines, Formatting.Indented)}; | |||||
/// </summary> | /// </summary> | ||||
/// <param name="groups">A list of source groups.</param> | /// <param name="groups">A list of source groups.</param> | ||||
/// <returns>A list of rule lines.</returns> | /// <returns>A list of rule lines.</returns> | ||||
private static List<string> GenerateBlockingRules(List<string> groups) | |||||
private List<string> GenerateBlockingRules(List<string> groups) | |||||
{ | { | ||||
List<string> ruleLines = new List<string>(); | List<string> ruleLines = new List<string>(); | ||||
foreach (var group in groups) | foreach (var group in groups) | ||||
@@ -337,7 +336,7 @@ var __RULES__ = {JsonConvert.SerializeObject(ruleLines, Formatting.Indented)}; | |||||
/// </summary> | /// </summary> | ||||
/// <param name="groups">A list of source groups.</param> | /// <param name="groups">A list of source groups.</param> | ||||
/// <returns>A list of rule lines.</returns> | /// <returns>A list of rule lines.</returns> | ||||
private static List<string> GenerateExceptionRules(List<string> groups) | |||||
private List<string> GenerateExceptionRules(List<string> groups) | |||||
=> GenerateBlockingRules(groups) | => GenerateBlockingRules(groups) | ||||
.Select(r => $"@@{r}") // convert blocking rules to exception rules | .Select(r => $"@@{r}") // convert blocking rules to exception rules | ||||
.ToList(); | .ToList(); | ||||
@@ -1,11 +1,6 @@ | |||||
using NLog; | |||||
using Shadowsocks.Model; | |||||
using Shadowsocks.Properties; | |||||
using Shadowsocks.Util; | |||||
using Splat; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
using System.IO; | using System.IO; | ||||
using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -15,14 +10,11 @@ namespace Shadowsocks.PAC | |||||
/// <summary> | /// <summary> | ||||
/// Processing the PAC file content | /// Processing the PAC file content | ||||
/// </summary> | /// </summary> | ||||
public class PACDaemon | |||||
public class PACDaemon : IEnableLogger | |||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public const string PAC_FILE = "pac.txt"; | public const string PAC_FILE = "pac.txt"; | ||||
public const string USER_RULE_FILE = "user-rule.txt"; | public const string USER_RULE_FILE = "user-rule.txt"; | ||||
public const string USER_ABP_FILE = "abp.txt"; | public const string USER_ABP_FILE = "abp.txt"; | ||||
private Configuration config; | |||||
FileSystemWatcher PACFileWatcher; | FileSystemWatcher PACFileWatcher; | ||||
FileSystemWatcher UserRuleFileWatcher; | FileSystemWatcher UserRuleFileWatcher; | ||||
@@ -30,14 +22,17 @@ namespace Shadowsocks.PAC | |||||
public event EventHandler PACFileChanged; | public event EventHandler PACFileChanged; | ||||
public event EventHandler UserRuleFileChanged; | public event EventHandler UserRuleFileChanged; | ||||
public PACDaemon(Configuration config) | |||||
private PACSettings _PACSettings; | |||||
private GeositeUpdater _geositeUpdater; | |||||
public PACDaemon(PACSettings pACSettings, string workingDirectory, string dlcPath) | |||||
{ | { | ||||
this.config = config; | |||||
_PACSettings = pACSettings; | |||||
_geositeUpdater = new GeositeUpdater(dlcPath); | |||||
TouchPACFile(); | TouchPACFile(); | ||||
TouchUserRuleFile(); | TouchUserRuleFile(); | ||||
this.WatchPacFile(); | |||||
this.WatchUserRuleFile(); | |||||
WatchPacFile(workingDirectory); | |||||
WatchUserRuleFile(workingDirectory); | |||||
} | } | ||||
@@ -45,7 +40,7 @@ namespace Shadowsocks.PAC | |||||
{ | { | ||||
if (!File.Exists(PAC_FILE)) | if (!File.Exists(PAC_FILE)) | ||||
{ | { | ||||
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect); | |||||
_geositeUpdater.MergeAndWritePACFile(_PACSettings.GeositeDirectGroups, _PACSettings.GeositeProxiedGroups, _PACSettings.PACDefaultToDirect); | |||||
} | } | ||||
return PAC_FILE; | return PAC_FILE; | ||||
} | } | ||||
@@ -54,7 +49,7 @@ namespace Shadowsocks.PAC | |||||
{ | { | ||||
if (!File.Exists(USER_RULE_FILE)) | if (!File.Exists(USER_RULE_FILE)) | ||||
{ | { | ||||
File.WriteAllText(USER_RULE_FILE, Resources.user_rule); | |||||
File.WriteAllText(USER_RULE_FILE, Properties.Resources.user_rule); | |||||
} | } | ||||
return USER_RULE_FILE; | return USER_RULE_FILE; | ||||
} | } | ||||
@@ -63,16 +58,16 @@ namespace Shadowsocks.PAC | |||||
{ | { | ||||
if (!File.Exists(PAC_FILE)) | if (!File.Exists(PAC_FILE)) | ||||
{ | { | ||||
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect); | |||||
_geositeUpdater.MergeAndWritePACFile(_PACSettings.GeositeDirectGroups, _PACSettings.GeositeProxiedGroups, _PACSettings.PACDefaultToDirect); | |||||
} | } | ||||
return File.ReadAllText(PAC_FILE, Encoding.UTF8); | return File.ReadAllText(PAC_FILE, Encoding.UTF8); | ||||
} | } | ||||
private void WatchPacFile() | |||||
private void WatchPacFile(string workingDirectory) | |||||
{ | { | ||||
PACFileWatcher?.Dispose(); | PACFileWatcher?.Dispose(); | ||||
PACFileWatcher = new FileSystemWatcher(Program.WorkingDirectory); | |||||
PACFileWatcher = new FileSystemWatcher(workingDirectory); | |||||
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | ||||
PACFileWatcher.Filter = PAC_FILE; | PACFileWatcher.Filter = PAC_FILE; | ||||
PACFileWatcher.Changed += PACFileWatcher_Changed; | PACFileWatcher.Changed += PACFileWatcher_Changed; | ||||
@@ -82,10 +77,10 @@ namespace Shadowsocks.PAC | |||||
PACFileWatcher.EnableRaisingEvents = true; | PACFileWatcher.EnableRaisingEvents = true; | ||||
} | } | ||||
private void WatchUserRuleFile() | |||||
private void WatchUserRuleFile(string workingDirectory) | |||||
{ | { | ||||
UserRuleFileWatcher?.Dispose(); | UserRuleFileWatcher?.Dispose(); | ||||
UserRuleFileWatcher = new FileSystemWatcher(Program.WorkingDirectory); | |||||
UserRuleFileWatcher = new FileSystemWatcher(workingDirectory); | |||||
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | ||||
UserRuleFileWatcher.Filter = USER_RULE_FILE; | UserRuleFileWatcher.Filter = USER_RULE_FILE; | ||||
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; | UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; | ||||
@@ -103,7 +98,7 @@ namespace Shadowsocks.PAC | |||||
{ | { | ||||
if (PACFileChanged != null) | if (PACFileChanged != null) | ||||
{ | { | ||||
logger.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
this.Log().Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
Task.Factory.StartNew(() => | Task.Factory.StartNew(() => | ||||
{ | { | ||||
((FileSystemWatcher)sender).EnableRaisingEvents = false; | ((FileSystemWatcher)sender).EnableRaisingEvents = false; | ||||
@@ -118,7 +113,7 @@ namespace Shadowsocks.PAC | |||||
{ | { | ||||
if (UserRuleFileChanged != null) | if (UserRuleFileChanged != null) | ||||
{ | { | ||||
logger.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
this.Log().Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||||
Task.Factory.StartNew(() => | Task.Factory.StartNew(() => | ||||
{ | { | ||||
((FileSystemWatcher)sender).EnableRaisingEvents = false; | ((FileSystemWatcher)sender).EnableRaisingEvents = false; | ||||
@@ -1,18 +1,17 @@ | |||||
using Shadowsocks.Net; | |||||
using Shadowsocks.Utilities; | |||||
using Shadowsocks.Net.Crypto; | |||||
using Splat; | |||||
using System; | using System; | ||||
using System.Net; | using System.Net; | ||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Text; | using System.Text; | ||||
using NLog; | |||||
using Shadowsocks.Net; | |||||
using Shadowsocks.Utilities; | |||||
using Shadowsocks.Net.Crypto; | |||||
using System.Reflection; | |||||
namespace Shadowsocks.PAC | namespace Shadowsocks.PAC | ||||
{ | { | ||||
public class PACServer : StreamService | |||||
public class PACServer : StreamService, IEnableLogger | |||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public const string RESOURCE_NAME = "pac"; | public const string RESOURCE_NAME = "pac"; | ||||
private string PacSecret | private string PacSecret | ||||
@@ -27,23 +26,23 @@ namespace Shadowsocks.PAC | |||||
} | } | ||||
} | } | ||||
private string _cachedPacSecret = ""; | private string _cachedPacSecret = ""; | ||||
private bool _PACServerEnableSecret; | |||||
public string PacUrl { get; private set; } = ""; | public string PacUrl { get; private set; } = ""; | ||||
private Configuration _config; | |||||
private PACDaemon _pacDaemon; | private PACDaemon _pacDaemon; | ||||
public PACServer(PACDaemon pacDaemon) | |||||
public PACServer(PACDaemon pacDaemon, bool PACServerEnableSecret) | |||||
{ | { | ||||
_pacDaemon = pacDaemon; | _pacDaemon = pacDaemon; | ||||
_PACServerEnableSecret = PACServerEnableSecret; | |||||
} | } | ||||
public void UpdatePACURL(Configuration config) | |||||
public void UpdatePACURL(EndPoint localEndPoint) | |||||
{ | { | ||||
_config = config; | |||||
string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : ""; | |||||
string usedSecret = _PACServerEnableSecret ? $"&secret={PacSecret}" : ""; | |||||
string contentHash = GetHash(_pacDaemon.GetPACContent()); | string contentHash = GetHash(_pacDaemon.GetPACContent()); | ||||
PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}"; | |||||
logger.Debug("Set PAC URL:" + PacUrl); | |||||
PacUrl = $"http://{localEndPoint}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}"; | |||||
this.Log().Debug("Setting PAC URL: {PacUrl}"); | |||||
} | } | ||||
private static string GetHash(string content) | private static string GetHash(string content) | ||||
@@ -81,7 +80,7 @@ namespace Shadowsocks.PAC | |||||
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 = !_config.secureLocalPac; | |||||
bool secretMatch = !_PACServerEnableSecret; | |||||
if (lines.Length < 2) // need at lease RequestLine + Host | if (lines.Length < 2) // need at lease RequestLine + Host | ||||
{ | { | ||||
@@ -172,7 +171,7 @@ namespace Shadowsocks.PAC | |||||
string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent(); | string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent(); | ||||
string responseHead = | string responseHead = | ||||
$@"HTTP/1.1 200 OK | $@"HTTP/1.1 200 OK | ||||
Server: ShadowsocksWindows/{UpdateChecker.Version} | |||||
Server: ShadowsocksPAC/{Assembly.GetExecutingAssembly().GetName().Version} | |||||
Content-Type: application/x-ns-proxy-autoconfig | Content-Type: application/x-ns-proxy-autoconfig | ||||
Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length} | Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length} | ||||
Connection: Close | Connection: Close | ||||
@@ -183,7 +182,7 @@ Connection: Close | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
logger.LogUsefulException(e); | |||||
this.Log().Error(e, ""); | |||||
socket.Close(); | socket.Close(); | ||||
} | } | ||||
} | } | ||||
@@ -199,11 +198,6 @@ Connection: Close | |||||
{ } | { } | ||||
} | } | ||||
private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) | |||||
{ | |||||
return localEndPoint.AddressFamily == AddressFamily.InterNetworkV6 | |||||
? $"{(useSocks ? "SOCKS5" : "PROXY")} [{localEndPoint.Address}]:{_config.localPort};" | |||||
: $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint.Address}:{_config.localPort};"; | |||||
} | |||||
private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) => $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint};"; | |||||
} | } | ||||
} | } |
@@ -0,0 +1,82 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace Shadowsocks.PAC | |||||
{ | |||||
/// <summary> | |||||
/// Settings used for PAC. | |||||
/// </summary> | |||||
public class PACSettings | |||||
{ | |||||
public PACSettings() | |||||
{ | |||||
PACDefaultToDirect = false; | |||||
PACServerEnableSecret = true; | |||||
RegeneratePacOnVersionUpdate = true; | |||||
CustomPACUrl = ""; | |||||
CustomGeositeUrl = ""; | |||||
GeositeDirectGroups = new List<string>() | |||||
{ | |||||
"private", | |||||
"cn", | |||||
"geolocation-!cn@cn", | |||||
}; | |||||
GeositeProxiedGroups = new List<string>() | |||||
{ | |||||
"geolocation-!cn", | |||||
}; | |||||
} | |||||
/// <summary> | |||||
/// Controls whether direct connection is used for | |||||
/// hostnames not matched by blocking rules | |||||
/// or matched by exception rules. | |||||
/// Defaults to false, or whitelist mode, | |||||
/// where hostnames matching the above conditions | |||||
/// are connected to via proxy. | |||||
/// Enable it to use blacklist mode. | |||||
/// </summary> | |||||
public bool PACDefaultToDirect { get; set; } | |||||
/// <summary> | |||||
/// Controls whether the PAC server uses a secret | |||||
/// to protect access to the PAC URL. | |||||
/// Defaults to true. | |||||
/// </summary> | |||||
public bool PACServerEnableSecret { get; set; } | |||||
/// <summary> | |||||
/// Controls whether `pac.txt` should be regenerated | |||||
/// when shadowsocks-windows is updated. | |||||
/// Defaults to true, so new changes can be applied. | |||||
/// Change it to false if you want to manage `pac.txt` | |||||
/// yourself. | |||||
/// </summary> | |||||
public bool RegeneratePacOnVersionUpdate { get; set; } | |||||
/// <summary> | |||||
/// Specifies a custom PAC URL. | |||||
/// Leave empty to use local PAC. | |||||
/// </summary> | |||||
public string CustomPACUrl { get; set; } | |||||
/// <summary> | |||||
/// Specifies a custom Geosite database URL. | |||||
/// Leave empty to use the default source. | |||||
/// </summary> | |||||
public string CustomGeositeUrl { get; set; } | |||||
/// <summary> | |||||
/// A list of Geosite groups | |||||
/// that we use direct connection for. | |||||
/// </summary> | |||||
public List<string> GeositeDirectGroups { get; set; } | |||||
/// <summary> | |||||
/// A list of Geosite groups | |||||
/// that we always connect to via proxy. | |||||
/// </summary> | |||||
public List<string> GeositeProxiedGroups { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,112 @@ | |||||
//------------------------------------------------------------------------------ | |||||
// <auto-generated> | |||||
// This code was generated by a tool. | |||||
// Runtime Version:4.0.30319.42000 | |||||
// | |||||
// Changes to this file may cause incorrect behavior and will be lost if | |||||
// the code is regenerated. | |||||
// </auto-generated> | |||||
//------------------------------------------------------------------------------ | |||||
namespace Shadowsocks.PAC.Properties { | |||||
using System; | |||||
/// <summary> | |||||
/// A strongly-typed resource class, for looking up localized strings, etc. | |||||
/// </summary> | |||||
// This class was auto-generated by the StronglyTypedResourceBuilder | |||||
// class via a tool like ResGen or Visual Studio. | |||||
// To add or remove a member, edit your .ResX file then rerun ResGen | |||||
// with the /str option, or rebuild your VS project. | |||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] | |||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] | |||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] | |||||
internal class Resources { | |||||
private static global::System.Resources.ResourceManager resourceMan; | |||||
private static global::System.Globalization.CultureInfo resourceCulture; | |||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | |||||
internal Resources() { | |||||
} | |||||
/// <summary> | |||||
/// Returns the cached ResourceManager instance used by this class. | |||||
/// </summary> | |||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | |||||
internal static global::System.Resources.ResourceManager ResourceManager { | |||||
get { | |||||
if (object.ReferenceEquals(resourceMan, null)) { | |||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.PAC.Properties.Resources", typeof(Resources).Assembly); | |||||
resourceMan = temp; | |||||
} | |||||
return resourceMan; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Overrides the current thread's CurrentUICulture property for all | |||||
/// resource lookups using this strongly typed resource class. | |||||
/// </summary> | |||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | |||||
internal static global::System.Globalization.CultureInfo Culture { | |||||
get { | |||||
return resourceCulture; | |||||
} | |||||
set { | |||||
resourceCulture = value; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Looks up a localized string similar to /* eslint-disable */ | |||||
///// Was generated by gfwlist2pac in precise mode | |||||
///// https://github.com/clowwindy/gfwlist2pac | |||||
/// | |||||
///// 2019-10-06: More 'javascript' way to interaction with main program | |||||
///// 2019-02-08: Updated to support shadowsocks-windows user rules. | |||||
/// | |||||
///var proxy = __PROXY__; | |||||
///var userrules = []; | |||||
///var rules = []; | |||||
/// | |||||
///// convert to abp grammar | |||||
///var re = /^(@@)?\|\|.*?[^\^]$/; | |||||
///for (var i = 0; i < __RULES__.length; i++) { | |||||
/// var s = __RULES__[i]; | |||||
/// if (s.match(re)) s += "^"; | |||||
/// rules.push(s); | |||||
///} | |||||
/// | |||||
/// [rest of string was truncated]";. | |||||
/// </summary> | |||||
internal static string abp { | |||||
get { | |||||
return ResourceManager.GetString("abp", resourceCulture); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Looks up a localized resource of type System.Byte[]. | |||||
/// </summary> | |||||
internal static byte[] dlc { | |||||
get { | |||||
object obj = ResourceManager.GetObject("dlc", resourceCulture); | |||||
return ((byte[])(obj)); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Looks up a localized string similar to ! Put user rules line by line in this file. | |||||
///! See https://adblockplus.org/en/filter-cheatsheet | |||||
///. | |||||
/// </summary> | |||||
internal static string user_rule { | |||||
get { | |||||
return ResourceManager.GetString("user_rule", resourceCulture); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,130 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<root> | |||||
<!-- | |||||
Microsoft ResX Schema | |||||
Version 2.0 | |||||
The primary goals of this format is to allow a simple XML format | |||||
that is mostly human readable. The generation and parsing of the | |||||
various data types are done through the TypeConverter classes | |||||
associated with the data types. | |||||
Example: | |||||
... ado.net/XML headers & schema ... | |||||
<resheader name="resmimetype">text/microsoft-resx</resheader> | |||||
<resheader name="version">2.0</resheader> | |||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | |||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | |||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | |||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | |||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | |||||
<value>[base64 mime encoded serialized .NET Framework object]</value> | |||||
</data> | |||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | |||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | |||||
<comment>This is a comment</comment> | |||||
</data> | |||||
There are any number of "resheader" rows that contain simple | |||||
name/value pairs. | |||||
Each data row contains a name, and value. The row also contains a | |||||
type or mimetype. Type corresponds to a .NET class that support | |||||
text/value conversion through the TypeConverter architecture. | |||||
Classes that don't support this are serialized and stored with the | |||||
mimetype set. | |||||
The mimetype is used for serialized objects, and tells the | |||||
ResXResourceReader how to depersist the object. This is currently not | |||||
extensible. For a given mimetype the value must be set accordingly: | |||||
Note - application/x-microsoft.net.object.binary.base64 is the format | |||||
that the ResXResourceWriter will generate, however the reader can | |||||
read any of the formats listed below. | |||||
mimetype: application/x-microsoft.net.object.binary.base64 | |||||
value : The object must be serialized with | |||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | |||||
: and then encoded with base64 encoding. | |||||
mimetype: application/x-microsoft.net.object.soap.base64 | |||||
value : The object must be serialized with | |||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter | |||||
: and then encoded with base64 encoding. | |||||
mimetype: application/x-microsoft.net.object.bytearray.base64 | |||||
value : The object must be serialized into a byte array | |||||
: using a System.ComponentModel.TypeConverter | |||||
: and then encoded with base64 encoding. | |||||
--> | |||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | |||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | |||||
<xsd:element name="root" msdata:IsDataSet="true"> | |||||
<xsd:complexType> | |||||
<xsd:choice maxOccurs="unbounded"> | |||||
<xsd:element name="metadata"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" use="required" type="xsd:string" /> | |||||
<xsd:attribute name="type" type="xsd:string" /> | |||||
<xsd:attribute name="mimetype" type="xsd:string" /> | |||||
<xsd:attribute ref="xml:space" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="assembly"> | |||||
<xsd:complexType> | |||||
<xsd:attribute name="alias" type="xsd:string" /> | |||||
<xsd:attribute name="name" type="xsd:string" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="data"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | |||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | |||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | |||||
<xsd:attribute ref="xml:space" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="resheader"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" type="xsd:string" use="required" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
</xsd:choice> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
</xsd:schema> | |||||
<resheader name="resmimetype"> | |||||
<value>text/microsoft-resx</value> | |||||
</resheader> | |||||
<resheader name="version"> | |||||
<value>2.0</value> | |||||
</resheader> | |||||
<resheader name="reader"> | |||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</resheader> | |||||
<resheader name="writer"> | |||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</resheader> | |||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||||
<data name="abp" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\abp.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value> | |||||
</data> | |||||
<data name="dlc" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\dlc.dat;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"> | |||||
<value>..\Resources\user-rule.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||||
</data> | |||||
</root> |
@@ -5,8 +5,36 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" /> | |||||
<None Remove="Resources\abp.js" /> | |||||
<None Remove="Resources\dlc.dat" /> | |||||
<None Remove="Resources\user-rule.txt" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" /> | <ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" /> | ||||
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" /> | |||||
<ProjectReference Include="..\Shadowsocks.Protobuf\Shadowsocks.Protobuf.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Resource Include="Resources\abp.js" /> | |||||
<Resource Include="Resources\dlc.dat" /> | |||||
<Resource Include="Resources\user-rule.txt" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Compile Update="Properties\Resources.Designer.cs"> | |||||
<DesignTime>True</DesignTime> | |||||
<AutoGen>True</AutoGen> | |||||
<DependentUpon>Resources.resx</DependentUpon> | |||||
</Compile> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<EmbeddedResource Update="Properties\Resources.resx"> | |||||
<Generator>ResXFileCodeGenerator</Generator> | |||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||||
</EmbeddedResource> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |