Browse Source

⛏ `Shadowsocks.PAC` now builds

pull/3006/head
database64128 3 years ago
parent
commit
d1f9bb89ed
No known key found for this signature in database GPG Key ID: 1CA27546BEDB8B01
7 changed files with 438 additions and 98 deletions
  1. +49
    -50
      Shadowsocks.PAC/GeositeUpdater.cs
  2. +19
    -24
      Shadowsocks.PAC/PACDaemon.cs
  3. +17
    -23
      Shadowsocks.PAC/PACServer.cs
  4. +82
    -0
      Shadowsocks.PAC/PACSettings.cs
  5. +112
    -0
      Shadowsocks.PAC/Properties/Resources.Designer.cs
  6. +130
    -0
      Shadowsocks.PAC/Properties/Resources.resx
  7. +29
    -1
      Shadowsocks.PAC/Shadowsocks.PAC.csproj

+ 49
- 50
Shadowsocks.PAC/GeositeUpdater.cs View File

@@ -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();


+ 19
- 24
Shadowsocks.PAC/PACDaemon.cs View File

@@ -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;


+ 17
- 23
Shadowsocks.PAC/PACServer.cs View File

@@ -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};";
} }
} }

+ 82
- 0
Shadowsocks.PAC/PACSettings.cs View File

@@ -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; }
}
}

+ 112
- 0
Shadowsocks.PAC/Properties/Resources.Designer.cs View File

@@ -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 &apos;javascript&apos; 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 &lt; __RULES__.length; i++) {
/// var s = __RULES__[i];
/// if (s.match(re)) s += &quot;^&quot;;
/// rules.push(s);
///}
///
/// [rest of string was truncated]&quot;;.
/// </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);
}
}
}
}

+ 130
- 0
Shadowsocks.PAC/Properties/Resources.resx View File

@@ -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>

+ 29
- 1
Shadowsocks.PAC/Shadowsocks.PAC.csproj View File

@@ -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>

Loading…
Cancel
Save