@@ -48,34 +48,34 @@ namespace Shadowsocks.Controller | |||
public static bool MergeAndWritePACFile(string 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) | |||
{ | |||
return false; | |||
} | |||
} | |||
File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); | |||
File.WriteAllText(PACDaemon.PAC_FILE, abpContent, Encoding.UTF8); | |||
return true; | |||
} | |||
private static string MergePACFile(string gfwListResult) | |||
{ | |||
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 | |||
{ | |||
abpContent = Utils.UnGzip(Resources.abp_js); | |||
abpContent = Resources.abp_js; | |||
} | |||
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); | |||
} | |||
@@ -0,0 +1,130 @@ | |||
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() | |||
{ | |||
TouchPACFile(); | |||
TouchUserRuleFile(); | |||
this.WatchPacFile(); | |||
this.WatchUserRuleFile(); | |||
} | |||
public string TouchPACFile() | |||
{ | |||
if (!File.Exists(PAC_FILE)) | |||
{ | |||
File.WriteAllText(PAC_FILE, Resources.proxy_pac_txt); | |||
} | |||
return PAC_FILE; | |||
} | |||
internal string TouchUserRuleFile() | |||
{ | |||
if (!File.Exists(USER_RULE_FILE)) | |||
{ | |||
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 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 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; } = ""; | |||
public string PacUrl { get; private set; } = ""; | |||
FileSystemWatcher PACFileWatcher; | |||
FileSystemWatcher UserRuleFileWatcher; | |||
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; | |||
@@ -51,7 +44,7 @@ namespace Shadowsocks.Controller | |||
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; | |||
} | |||
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[] lines = request.Split('\r', '\n'); | |||
bool hostMatch = false, pathMatch = false, useSocks = false; | |||
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[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 (!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) | |||
{ | |||
@@ -174,7 +164,7 @@ namespace Shadowsocks.Controller | |||
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 | |||
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) | |||
{ | |||
@@ -28,6 +28,7 @@ namespace Shadowsocks.Controller | |||
private Thread _trafficThread; | |||
private Listener _listener; | |||
private PACDaemon _pacDaemon; | |||
private PACServer _pacServer; | |||
private Configuration _config; | |||
private StrategyManager _strategyManager; | |||
@@ -299,14 +300,14 @@ namespace Shadowsocks.Controller | |||
public void TouchPACFile() | |||
{ | |||
string pacFilename = _pacServer.TouchPACFile(); | |||
string pacFilename = _pacDaemon.TouchPACFile(); | |||
PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename }); | |||
} | |||
public void TouchUserRuleFile() | |||
{ | |||
string userRuleFilename = _pacServer.TouchUserRuleFile(); | |||
string userRuleFilename = _pacDaemon.TouchUserRuleFile(); | |||
UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename }); | |||
} | |||
@@ -468,13 +469,20 @@ namespace Shadowsocks.Controller | |||
{ | |||
privoxyRunner = new PrivoxyRunner(); | |||
} | |||
if (_pacDaemon == null) | |||
{ | |||
_pacDaemon = new PACDaemon(); | |||
_pacDaemon.PACFileChanged += PacDaemon_PACFileChanged; | |||
_pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged; | |||
} | |||
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) | |||
{ | |||
gfwListUpdater = new GFWListUpdater(); | |||
@@ -561,7 +569,7 @@ namespace Shadowsocks.Controller | |||
SystemProxy.Update(_config, false, _pacServer); | |||
} | |||
private void PacServer_PACFileChanged(object sender, EventArgs e) | |||
private void PacDaemon_PACFileChanged(object sender, EventArgs e) | |||
{ | |||
UpdateSystemProxy(); | |||
} | |||
@@ -577,7 +585,7 @@ namespace Shadowsocks.Controller | |||
} | |||
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"))) | |||
{ | |||
@@ -0,0 +1,792 @@ | |||
// Generated by gfwlist2pac in precise mode | |||
// https://github.com/clowwindy/gfwlist2pac | |||
// 2019-02-08: Updated to support shadowsocks-windows user rules. | |||
var proxy = "__PROXY__"; | |||
var userrules = __USERRULES__; | |||
var rules = __RULES__; | |||
/* | |||
* This file is part of Adblock Plus <http://adblockplus.org/>, | |||
* Copyright (C) 2006-2014 Eyeo GmbH | |||
* | |||
* Adblock Plus is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License version 3 as | |||
* published by the Free Software Foundation. | |||
* | |||
* Adblock Plus is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
function createDict() | |||
{ | |||
var result = {}; | |||
result.__proto__ = null; | |||
return result; | |||
} | |||
function getOwnPropertyDescriptor(obj, key) | |||
{ | |||
if (obj.hasOwnProperty(key)) | |||
{ | |||
return obj[key]; | |||
} | |||
return null; | |||
} | |||
function extend(subclass, superclass, definition) | |||
{ | |||
if (Object.__proto__) | |||
{ | |||
definition.__proto__ = superclass.prototype; | |||
subclass.prototype = definition; | |||
} | |||
else | |||
{ | |||
var tmpclass = function(){}, ret; | |||
tmpclass.prototype = superclass.prototype; | |||
subclass.prototype = new tmpclass(); | |||
subclass.prototype.constructor = superclass; | |||
for (var i in definition) | |||
{ | |||
if (definition.hasOwnProperty(i)) | |||
{ | |||
subclass.prototype[i] = definition[i]; | |||
} | |||
} | |||
} | |||
} | |||
function Filter(text) | |||
{ | |||
this.text = text; | |||
this.subscriptions = []; | |||
} | |||
Filter.prototype = { | |||
text: null, | |||
subscriptions: null, | |||
toString: function() | |||
{ | |||
return this.text; | |||
} | |||
}; | |||
Filter.knownFilters = createDict(); | |||
Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/; | |||
Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/; | |||
Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/; | |||
Filter.fromText = function(text) | |||
{ | |||
if (text in Filter.knownFilters) | |||
{ | |||
return Filter.knownFilters[text]; | |||
} | |||
var ret; | |||
if (text.charAt(0) == "!") | |||
{ | |||
ret = new CommentFilter(text); | |||
} | |||
else | |||
{ | |||
ret = RegExpFilter.fromText(text); | |||
} | |||
Filter.knownFilters[ret.text] = ret; | |||
return ret; | |||
}; | |||
function InvalidFilter(text, reason) | |||
{ | |||
Filter.call(this, text); | |||
this.reason = reason; | |||
} | |||
extend(InvalidFilter, Filter, { | |||
reason: null | |||
}); | |||
function CommentFilter(text) | |||
{ | |||
Filter.call(this, text); | |||
} | |||
extend(CommentFilter, Filter, { | |||
}); | |||
function ActiveFilter(text, domains) | |||
{ | |||
Filter.call(this, text); | |||
this.domainSource = domains; | |||
} | |||
extend(ActiveFilter, Filter, { | |||
domainSource: null, | |||
domainSeparator: null, | |||
ignoreTrailingDot: true, | |||
domainSourceIsUpperCase: false, | |||
getDomains: function() | |||
{ | |||
var prop = getOwnPropertyDescriptor(this, "domains"); | |||
if (prop) | |||
{ | |||
return prop; | |||
} | |||
var domains = null; | |||
if (this.domainSource) | |||
{ | |||
var source = this.domainSource; | |||
if (!this.domainSourceIsUpperCase) | |||
{ | |||
source = source.toUpperCase(); | |||
} | |||
var list = source.split(this.domainSeparator); | |||
if (list.length == 1 && (list[0]).charAt(0) != "~") | |||
{ | |||
domains = createDict(); | |||
domains[""] = false; | |||
if (this.ignoreTrailingDot) | |||
{ | |||
list[0] = list[0].replace(/\.+$/, ""); | |||
} | |||
domains[list[0]] = true; | |||
} | |||
else | |||
{ | |||
var hasIncludes = false; | |||
for (var i = 0; i < list.length; i++) | |||
{ | |||
var domain = list[i]; | |||
if (this.ignoreTrailingDot) | |||
{ | |||
domain = domain.replace(/\.+$/, ""); | |||
} | |||
if (domain == "") | |||
{ | |||
continue; | |||
} | |||
var include; | |||
if (domain.charAt(0) == "~") | |||
{ | |||
include = false; | |||
domain = domain.substr(1); | |||
} | |||
else | |||
{ | |||
include = true; | |||
hasIncludes = true; | |||
} | |||
if (!domains) | |||
{ | |||
domains = createDict(); | |||
} | |||
domains[domain] = include; | |||
} | |||
domains[""] = !hasIncludes; | |||
} | |||
this.domainSource = null; | |||
} | |||
return this.domains; | |||
}, | |||
sitekeys: null, | |||
isActiveOnDomain: function(docDomain, sitekey) | |||
{ | |||
if (this.getSitekeys() && (!sitekey || this.getSitekeys().indexOf(sitekey.toUpperCase()) < 0)) | |||
{ | |||
return false; | |||
} | |||
if (!this.getDomains()) | |||
{ | |||
return true; | |||
} | |||
if (!docDomain) | |||
{ | |||
return this.getDomains()[""]; | |||
} | |||
if (this.ignoreTrailingDot) | |||
{ | |||
docDomain = docDomain.replace(/\.+$/, ""); | |||
} | |||
docDomain = docDomain.toUpperCase(); | |||
while (true) | |||
{ | |||
if (docDomain in this.getDomains()) | |||
{ | |||
return this.domains[docDomain]; | |||
} | |||
var nextDot = docDomain.indexOf("."); | |||
if (nextDot < 0) | |||
{ | |||
break; | |||
} | |||
docDomain = docDomain.substr(nextDot + 1); | |||
} | |||
return this.domains[""]; | |||
}, | |||
isActiveOnlyOnDomain: function(docDomain) | |||
{ | |||
if (!docDomain || !this.getDomains() || this.getDomains()[""]) | |||
{ | |||
return false; | |||
} | |||
if (this.ignoreTrailingDot) | |||
{ | |||
docDomain = docDomain.replace(/\.+$/, ""); | |||
} | |||
docDomain = docDomain.toUpperCase(); | |||
for (var domain in this.getDomains()) | |||
{ | |||
if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1)) | |||
{ | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
}); | |||
function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys) | |||
{ | |||
ActiveFilter.call(this, text, domains, sitekeys); | |||
if (contentType != null) | |||
{ | |||
this.contentType = contentType; | |||
} | |||
if (matchCase) | |||
{ | |||
this.matchCase = matchCase; | |||
} | |||
if (thirdParty != null) | |||
{ | |||
this.thirdParty = thirdParty; | |||
} | |||
if (sitekeys != null) | |||
{ | |||
this.sitekeySource = sitekeys; | |||
} | |||
if (regexpSource.length >= 2 && regexpSource.charAt(0) == "/" && regexpSource.charAt(regexpSource.length - 1) == "/") | |||
{ | |||
var regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i"); | |||
this.regexp = regexp; | |||
} | |||
else | |||
{ | |||
this.regexpSource = regexpSource; | |||
} | |||
} | |||
extend(RegExpFilter, ActiveFilter, { | |||
domainSourceIsUpperCase: true, | |||
length: 1, | |||
domainSeparator: "|", | |||
regexpSource: null, | |||
getRegexp: function() | |||
{ | |||
var prop = getOwnPropertyDescriptor(this, "regexp"); | |||
if (prop) | |||
{ | |||
return prop; | |||
} | |||
var source = this.regexpSource.replace(/\*+/g, "*").replace(/\^\|$/, "^").replace(/\W/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x7F]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?").replace(/^\\\|/, "^").replace(/\\\|$/, "$").replace(/^(\.\*)/, "").replace(/(\.\*)$/, ""); | |||
var regexp = new RegExp(source, this.matchCase ? "" : "i"); | |||
this.regexp = regexp; | |||
return regexp; | |||
}, | |||
contentType: 2147483647, | |||
matchCase: false, | |||
thirdParty: null, | |||
sitekeySource: null, | |||
getSitekeys: function() | |||
{ | |||
var prop = getOwnPropertyDescriptor(this, "sitekeys"); | |||
if (prop) | |||
{ | |||
return prop; | |||
} | |||
var sitekeys = null; | |||
if (this.sitekeySource) | |||
{ | |||
sitekeys = this.sitekeySource.split("|"); | |||
this.sitekeySource = null; | |||
} | |||
this.sitekeys = sitekeys; | |||
return this.sitekeys; | |||
}, | |||
matches: function(location, contentType, docDomain, thirdParty, sitekey) | |||
{ | |||
if (this.getRegexp().test(location) && this.isActiveOnDomain(docDomain, sitekey)) | |||
{ | |||
return true; | |||
} | |||
return false; | |||
} | |||
}); | |||
RegExpFilter.prototype["0"] = "#this"; | |||
RegExpFilter.fromText = function(text) | |||
{ | |||
var blocking = true; | |||
var origText = text; | |||
if (text.indexOf("@@") == 0) | |||
{ | |||
blocking = false; | |||
text = text.substr(2); | |||
} | |||
var contentType = null; | |||
var matchCase = null; | |||
var domains = null; | |||
var sitekeys = null; | |||
var thirdParty = null; | |||
var collapse = null; | |||
var options; | |||
var match = text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null; | |||
if (match) | |||
{ | |||
options = match[1].toUpperCase().split(","); | |||
text = match.input.substr(0, match.index); | |||
for (var _loopIndex6 = 0; _loopIndex6 < options.length; ++_loopIndex6) | |||
{ | |||
var option = options[_loopIndex6]; | |||
var value = null; | |||
var separatorIndex = option.indexOf("="); | |||
if (separatorIndex >= 0) | |||
{ | |||
value = option.substr(separatorIndex + 1); | |||
option = option.substr(0, separatorIndex); | |||
} | |||
option = option.replace(/-/, "_"); | |||
if (option in RegExpFilter.typeMap) | |||
{ | |||
if (contentType == null) | |||
{ | |||
contentType = 0; | |||
} | |||
contentType |= RegExpFilter.typeMap[option]; | |||
} | |||
else if (option.charAt(0) == "~" && option.substr(1) in RegExpFilter.typeMap) | |||
{ | |||
if (contentType == null) | |||
{ | |||
contentType = RegExpFilter.prototype.contentType; | |||
} | |||
contentType &= ~RegExpFilter.typeMap[option.substr(1)]; | |||
} | |||
else if (option == "MATCH_CASE") | |||
{ | |||
matchCase = true; | |||
} | |||
else if (option == "~MATCH_CASE") | |||
{ | |||
matchCase = false; | |||
} | |||
else if (option == "DOMAIN" && typeof value != "undefined") | |||
{ | |||
domains = value; | |||
} | |||
else if (option == "THIRD_PARTY") | |||
{ | |||
thirdParty = true; | |||
} | |||
else if (option == "~THIRD_PARTY") | |||
{ | |||
thirdParty = false; | |||
} | |||
else if (option == "COLLAPSE") | |||
{ | |||
collapse = true; | |||
} | |||
else if (option == "~COLLAPSE") | |||
{ | |||
collapse = false; | |||
} | |||
else if (option == "SITEKEY" && typeof value != "undefined") | |||
{ | |||
sitekeys = value; | |||
} | |||
else | |||
{ | |||
return new InvalidFilter(origText, "Unknown option " + option.toLowerCase()); | |||
} | |||
} | |||
} | |||
if (!blocking && (contentType == null || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text)) | |||
{ | |||
if (contentType == null) | |||
{ | |||
contentType = RegExpFilter.prototype.contentType; | |||
} | |||
contentType &= ~RegExpFilter.typeMap.DOCUMENT; | |||
} | |||
try | |||
{ | |||
if (blocking) | |||
{ | |||
return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys, collapse); | |||
} | |||
else | |||
{ | |||
return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, sitekeys); | |||
} | |||
} | |||
catch (e) | |||
{ | |||
return new InvalidFilter(origText, e); | |||
} | |||
}; | |||
RegExpFilter.typeMap = { | |||
OTHER: 1, | |||
SCRIPT: 2, | |||
IMAGE: 4, | |||
STYLESHEET: 8, | |||
OBJECT: 16, | |||
SUBDOCUMENT: 32, | |||
DOCUMENT: 64, | |||
XBL: 1, | |||
PING: 1, | |||
XMLHTTPREQUEST: 2048, | |||
OBJECT_SUBREQUEST: 4096, | |||
DTD: 1, | |||
MEDIA: 16384, | |||
FONT: 32768, | |||
BACKGROUND: 4, | |||
POPUP: 268435456, | |||
ELEMHIDE: 1073741824 | |||
}; | |||
RegExpFilter.prototype.contentType &= ~ (RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.POPUP); | |||
function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys, collapse) | |||
{ | |||
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys); | |||
this.collapse = collapse; | |||
} | |||
extend(BlockingFilter, RegExpFilter, { | |||
collapse: null | |||
}); | |||
function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys) | |||
{ | |||
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty, sitekeys); | |||
} | |||
extend(WhitelistFilter, RegExpFilter, { | |||
}); | |||
function Matcher() | |||
{ | |||
this.clear(); | |||
} | |||
Matcher.prototype = { | |||
filterByKeyword: null, | |||
keywordByFilter: null, | |||
clear: function() | |||
{ | |||
this.filterByKeyword = createDict(); | |||
this.keywordByFilter = createDict(); | |||
}, | |||
add: function(filter) | |||
{ | |||
if (filter.text in this.keywordByFilter) | |||
{ | |||
return; | |||
} | |||
var keyword = this.findKeyword(filter); | |||
var oldEntry = this.filterByKeyword[keyword]; | |||
if (typeof oldEntry == "undefined") | |||
{ | |||
this.filterByKeyword[keyword] = filter; | |||
} | |||
else if (oldEntry.length == 1) | |||
{ | |||
this.filterByKeyword[keyword] = [oldEntry, filter]; | |||
} | |||
else | |||
{ | |||
oldEntry.push(filter); | |||
} | |||
this.keywordByFilter[filter.text] = keyword; | |||
}, | |||
remove: function(filter) | |||
{ | |||
if (!(filter.text in this.keywordByFilter)) | |||
{ | |||
return; | |||
} | |||
var keyword = this.keywordByFilter[filter.text]; | |||
var list = this.filterByKeyword[keyword]; | |||
if (list.length <= 1) | |||
{ | |||
delete this.filterByKeyword[keyword]; | |||
} | |||
else | |||
{ | |||
var index = list.indexOf(filter); | |||
if (index >= 0) | |||
{ | |||
list.splice(index, 1); | |||
if (list.length == 1) | |||
{ | |||
this.filterByKeyword[keyword] = list[0]; | |||
} | |||
} | |||
} | |||
delete this.keywordByFilter[filter.text]; | |||
}, | |||
findKeyword: function(filter) | |||
{ | |||
var result = ""; | |||
var text = filter.text; | |||
if (Filter.regexpRegExp.test(text)) | |||
{ | |||
return result; | |||
} | |||
var match = Filter.optionsRegExp.exec(text); | |||
if (match) | |||
{ | |||
text = match.input.substr(0, match.index); | |||
} | |||
if (text.substr(0, 2) == "@@") | |||
{ | |||
text = text.substr(2); | |||
} | |||
var candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g); | |||
if (!candidates) | |||
{ | |||
return result; | |||
} | |||
var hash = this.filterByKeyword; | |||
var resultCount = 16777215; | |||
var resultLength = 0; | |||
for (var i = 0, l = candidates.length; i < l; i++) | |||
{ | |||
var candidate = candidates[i].substr(1); | |||
var count = candidate in hash ? hash[candidate].length : 0; | |||
if (count < resultCount || count == resultCount && candidate.length > resultLength) | |||
{ | |||
result = candidate; | |||
resultCount = count; | |||
resultLength = candidate.length; | |||
} | |||
} | |||
return result; | |||
}, | |||
hasFilter: function(filter) | |||
{ | |||
return filter.text in this.keywordByFilter; | |||
}, | |||
getKeywordForFilter: function(filter) | |||
{ | |||
if (filter.text in this.keywordByFilter) | |||
{ | |||
return this.keywordByFilter[filter.text]; | |||
} | |||
else | |||
{ | |||
return null; | |||
} | |||
}, | |||
_checkEntryMatch: function(keyword, location, contentType, docDomain, thirdParty, sitekey) | |||
{ | |||
var list = this.filterByKeyword[keyword]; | |||
for (var i = 0; i < list.length; i++) | |||
{ | |||
var filter = list[i]; | |||
if (filter == "#this") | |||
{ | |||
filter = list; | |||
} | |||
if (filter.matches(location, contentType, docDomain, thirdParty, sitekey)) | |||
{ | |||
return filter; | |||
} | |||
} | |||
return null; | |||
}, | |||
matchesAny: function(location, contentType, docDomain, thirdParty, sitekey) | |||
{ | |||
var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); | |||
if (candidates === null) | |||
{ | |||
candidates = []; | |||
} | |||
candidates.push(""); | |||
for (var i = 0, l = candidates.length; i < l; i++) | |||
{ | |||
var substr = candidates[i]; | |||
if (substr in this.filterByKeyword) | |||
{ | |||
var result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey); | |||
if (result) | |||
{ | |||
return result; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
}; | |||
function CombinedMatcher() | |||
{ | |||
this.blacklist = new Matcher(); | |||
this.whitelist = new Matcher(); | |||
this.resultCache = createDict(); | |||
} | |||
CombinedMatcher.maxCacheEntries = 1000; | |||
CombinedMatcher.prototype = { | |||
blacklist: null, | |||
whitelist: null, | |||
resultCache: null, | |||
cacheEntries: 0, | |||
clear: function() | |||
{ | |||
this.blacklist.clear(); | |||
this.whitelist.clear(); | |||
this.resultCache = createDict(); | |||
this.cacheEntries = 0; | |||
}, | |||
add: function(filter) | |||
{ | |||
if (filter instanceof WhitelistFilter) | |||
{ | |||
this.whitelist.add(filter); | |||
} | |||
else | |||
{ | |||
this.blacklist.add(filter); | |||
} | |||
if (this.cacheEntries > 0) | |||
{ | |||
this.resultCache = createDict(); | |||
this.cacheEntries = 0; | |||
} | |||
}, | |||
remove: function(filter) | |||
{ | |||
if (filter instanceof WhitelistFilter) | |||
{ | |||
this.whitelist.remove(filter); | |||
} | |||
else | |||
{ | |||
this.blacklist.remove(filter); | |||
} | |||
if (this.cacheEntries > 0) | |||
{ | |||
this.resultCache = createDict(); | |||
this.cacheEntries = 0; | |||
} | |||
}, | |||
findKeyword: function(filter) | |||
{ | |||
if (filter instanceof WhitelistFilter) | |||
{ | |||
return this.whitelist.findKeyword(filter); | |||
} | |||
else | |||
{ | |||
return this.blacklist.findKeyword(filter); | |||
} | |||
}, | |||
hasFilter: function(filter) | |||
{ | |||
if (filter instanceof WhitelistFilter) | |||
{ | |||
return this.whitelist.hasFilter(filter); | |||
} | |||
else | |||
{ | |||
return this.blacklist.hasFilter(filter); | |||
} | |||
}, | |||
getKeywordForFilter: function(filter) | |||
{ | |||
if (filter instanceof WhitelistFilter) | |||
{ | |||
return this.whitelist.getKeywordForFilter(filter); | |||
} | |||
else | |||
{ | |||
return this.blacklist.getKeywordForFilter(filter); | |||
} | |||
}, | |||
isSlowFilter: function(filter) | |||
{ | |||
var matcher = filter instanceof WhitelistFilter ? this.whitelist : this.blacklist; | |||
if (matcher.hasFilter(filter)) | |||
{ | |||
return !matcher.getKeywordForFilter(filter); | |||
} | |||
else | |||
{ | |||
return !matcher.findKeyword(filter); | |||
} | |||
}, | |||
matchesAnyInternal: function(location, contentType, docDomain, thirdParty, sitekey) | |||
{ | |||
var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); | |||
if (candidates === null) | |||
{ | |||
candidates = []; | |||
} | |||
candidates.push(""); | |||
var blacklistHit = null; | |||
for (var i = 0, l = candidates.length; i < l; i++) | |||
{ | |||
var substr = candidates[i]; | |||
if (substr in this.whitelist.filterByKeyword) | |||
{ | |||
var result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey); | |||
if (result) | |||
{ | |||
return result; | |||
} | |||
} | |||
if (substr in this.blacklist.filterByKeyword && blacklistHit === null) | |||
{ | |||
blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty, sitekey); | |||
} | |||
} | |||
return blacklistHit; | |||
}, | |||
matchesAny: function(location, docDomain) | |||
{ | |||
var key = location + " " + docDomain + " "; | |||
if (key in this.resultCache) | |||
{ | |||
return this.resultCache[key]; | |||
} | |||
var result = this.matchesAnyInternal(location, 0, docDomain, null, null); | |||
if (this.cacheEntries >= CombinedMatcher.maxCacheEntries) | |||
{ | |||
this.resultCache = createDict(); | |||
this.cacheEntries = 0; | |||
} | |||
this.resultCache[key] = result; | |||
this.cacheEntries++; | |||
return result; | |||
} | |||
}; | |||
var userrulesMatcher = new CombinedMatcher(); | |||
var defaultMatcher = new CombinedMatcher(); | |||
var direct = 'DIRECT;'; | |||
for (var i = 0; i < userrules.length; i++) { | |||
userrulesMatcher.add(Filter.fromText(userrules[i])); | |||
} | |||
for (var i = 0; i < rules.length; i++) { | |||
defaultMatcher.add(Filter.fromText(rules[i])); | |||
} | |||
function FindProxyForURL(url, host) { | |||
if (userrulesMatcher.matchesAny(url, host) instanceof BlockingFilter) { | |||
return proxy; | |||
} | |||
if (userrulesMatcher.matchesAny(url, host) instanceof WhitelistFilter) { | |||
return direct; | |||
} | |||
if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) { | |||
return proxy; | |||
} | |||
return direct; | |||
} |
@@ -61,12 +61,27 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// Looks up a localized string similar to // Generated by gfwlist2pac in precise mode | |||
///// https://github.com/clowwindy/gfwlist2pac | |||
/// | |||
///// 2019-02-08: Updated to support shadowsocks-windows user rules. | |||
/// | |||
///var proxy = "__PROXY__"; | |||
/// | |||
///var userrules = __USERRULES__; | |||
///var rules = __RULES__; | |||
/// | |||
////* | |||
///* This file is part of Adblock Plus <http://adblockplus.org/>, | |||
///* Copyright (C) 2006-2014 Eyeo GmbH | |||
///* | |||
///* Adblock Plus is free software: you can redistribute it and/or modify | |||
///* it under the terms of the GNU General Public License version 3 as | |||
///* published by t [rest of string was truncated]";. | |||
/// </summary> | |||
internal static byte[] abp_js { | |||
internal static string abp_js { | |||
get { | |||
object obj = ResourceManager.GetObject("abp_js", resourceCulture); | |||
return ((byte[])(obj)); | |||
return ResourceManager.GetString("abp_js", resourceCulture); | |||
} | |||
} | |||
@@ -138,12 +153,33 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// Looks up a localized string similar to // Generated by gfwlist2pac in precise mode | |||
///// https://github.com/clowwindy/gfwlist2pac | |||
/// | |||
///// 2019-02-08: Updated to support shadowsocks-windows user rules. | |||
/// | |||
///var proxy = "__PROXY__"; | |||
/// | |||
///var userrules = []; | |||
///var rules = [ | |||
/// "|http://85.17.73.31/", | |||
/// "||agnesb.fr", | |||
/// "||akiba-web.com", | |||
/// "||altrec.com", | |||
/// "||angela-merkel.de", | |||
/// "||angola.org", | |||
/// "||apartmentratings.com", | |||
/// "||apartments.com", | |||
/// "||arena.taipei", | |||
/// "||asianspiss.com", | |||
/// "||assimp.org", | |||
/// "||athenaeizou.com", | |||
/// "||azubu.tv", | |||
/// [rest of string was truncated]";. | |||
/// </summary> | |||
internal static byte[] proxy_pac_txt { | |||
internal static string proxy_pac_txt { | |||
get { | |||
object obj = ResourceManager.GetObject("proxy_pac_txt", resourceCulture); | |||
return ((byte[])(obj)); | |||
return ResourceManager.GetString("proxy_pac_txt", resourceCulture); | |||
} | |||
} | |||
@@ -119,7 +119,7 @@ | |||
</resheader> | |||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||
<data name="abp_js" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\abp.js.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
<value>..\Data\abp.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value> | |||
</data> | |||
<data name="ja" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\ja.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||
@@ -134,7 +134,7 @@ | |||
<value>..\data\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="proxy_pac_txt" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\proxy.pac.txt.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
<value>..\Data\proxy.pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||
</data> | |||
<data name="ss32Fill" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Resources\ss32Fill.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | |||
@@ -103,6 +103,7 @@ | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="Controller\HotkeyReg.cs" /> | |||
<Compile Include="Controller\Service\PACDaemon.cs" /> | |||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADEncryptor.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" /> | |||
@@ -255,10 +256,8 @@ | |||
<None Include="app.manifest"> | |||
<SubType>Designer</SubType> | |||
</None> | |||
<None Include="Data\abp.js.gz" /> | |||
<None Include="Data\libsscrypto.dll.gz" /> | |||
<None Include="Data\privoxy.exe.gz" /> | |||
<None Include="Data\proxy.pac.txt.gz" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="Data\sysproxy.exe.gz" /> | |||
@@ -270,6 +269,8 @@ | |||
<LastGenOutput>Settings.Designer.cs</LastGenOutput> | |||
</None> | |||
<None Include="Resources\ssw128.png" /> | |||
<Content Include="Data\abp.js" /> | |||
<Content Include="Data\proxy.pac.txt" /> | |||
<Content Include="Data\zh_CN.txt" /> | |||
<Content Include="Data\zh_TW.txt" /> | |||
<Content Include="Data\ja.txt" /> | |||