代码大清理,删除已经不再使用的代码;国内IP段改成从Loyalsoldier/geoip项目获取;支持IPv6;支持纯IP HOST的规则查找;IP查找使用前缀树;尽量减少生成的pac文件体积;优化规则判断;美化README;
This commit is contained in:
223
pac-template
223
pac-template
@@ -8,78 +8,81 @@ var domainsUsingProxy = __DOMAINS__;
|
||||
|
||||
var localTlds = __LOCAL_TLDS__;
|
||||
|
||||
var cnips = __CN_IPS__;
|
||||
var cidrs = __CIDRS__;
|
||||
|
||||
var hasOwnProperty = Object.hasOwnProperty;
|
||||
|
||||
var ipRegExp = new RegExp(/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/);
|
||||
|
||||
function isIPv6(ip) {
|
||||
// Split the IP address into groups of hexadecimal digits
|
||||
const groups = ip.split(':');
|
||||
|
||||
// An IPv6 address must have at least one group and at most 8 groups
|
||||
if (groups.length < 1 || groups.length > 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that each group is a valid hexadecimal number
|
||||
for (const group of groups) {
|
||||
// Check that the group is not null, undefined, or an empty string before calling parseInt()
|
||||
if (group === null || group === undefined || group === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use parseInt() to check if the group is a valid hexadecimal number
|
||||
const value = parseInt(group, 16);
|
||||
if (isNaN(value) || value < 0 || value > 0xFFFF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the address contains a double colon, ensure that it appears only once
|
||||
if (ip.includes('::')) {
|
||||
if (ip.indexOf('::') !== ip.lastIndexOf('::')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The address is valid if it passes all the checks
|
||||
return true;
|
||||
function isIpAddress(ip) {
|
||||
return /^\d{1,3}(\.\d{1,3}){3}$/.test(ip) || /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(ip);
|
||||
}
|
||||
|
||||
function convertAddress(ipchars) {
|
||||
var bytes = ipchars.split('.');
|
||||
var result = ((bytes[0] & 0xff) << 24) |
|
||||
((bytes[1] & 0xff) << 16) |
|
||||
((bytes[2] & 0xff) << 8) |
|
||||
(bytes[3] & 0xff);
|
||||
return result;
|
||||
function RadixTree() {
|
||||
this.root = {};
|
||||
}
|
||||
|
||||
function match(ip) {
|
||||
var left = 0, right = cnips.length;
|
||||
do {
|
||||
var mid = Math.floor((left + right) / 2),
|
||||
ipf = (ip & cnips[mid][1]) >>> 0,
|
||||
m = (cnips[mid][0] & cnips[mid][1]) >>> 0;
|
||||
if (ipf == m) {
|
||||
return true;
|
||||
} else if (ipf > m) {
|
||||
left = mid + 1;
|
||||
RadixTree.prototype.insert = function(string) {
|
||||
var node = this.root;
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
var char = string[i];
|
||||
if (!node[char]) {
|
||||
node[char] = {};
|
||||
}
|
||||
node = node[char];
|
||||
}
|
||||
};
|
||||
|
||||
RadixTree.prototype.to_list = function() {
|
||||
return this.root;
|
||||
};
|
||||
|
||||
function ipToBinary(ip) {
|
||||
// Check if it's IPv4
|
||||
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
||||
return ip.split('.').map(function(num) {
|
||||
return ("00000000" + parseInt(num, 10).toString(2)).slice(-8);
|
||||
}).join('');
|
||||
} else if (/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(ip)) {
|
||||
// Expand the IPv6 address if it contains '::'
|
||||
var parts = ip.split('::');
|
||||
var left = parts[0] ? parts[0].split(':') : [];
|
||||
var right = parts[1] ? parts[1].split(':') : [];
|
||||
|
||||
// Calculate the number of zero groups to insert
|
||||
var zeroGroups = 8 - (left.length + right.length);
|
||||
|
||||
// Create the full address by inserting zero groups
|
||||
var fullAddress = left.concat(Array(zeroGroups + 1).join('0').split('')).concat(right);
|
||||
|
||||
// Convert each group to binary and pad to 16 bits
|
||||
return fullAddress.map(function(group) {
|
||||
return ("0000000000000000" + parseInt(group || '0', 16).toString(2)).slice(-16);
|
||||
}).join('');
|
||||
}
|
||||
}
|
||||
|
||||
function searchRadixTree(bits) {
|
||||
var currentNode = radixTree;
|
||||
var string = '';
|
||||
let isLastNode = false;
|
||||
for (var i=0; i<bits.length; i++) {
|
||||
var char = bits[i];
|
||||
string += char;
|
||||
if (currentNode[char]) {
|
||||
currentNode = currentNode[char];
|
||||
isLastNode = Object.keys(currentNode).every(function(key) {
|
||||
return !currentNode[key]
|
||||
})
|
||||
} else {
|
||||
right = mid;
|
||||
break;
|
||||
}
|
||||
} while (left + 1 <= right)
|
||||
return false;
|
||||
}
|
||||
|
||||
function isInDirectDomain(host) {
|
||||
if (hasOwnProperty.call(directDomains, host)) {
|
||||
return true;
|
||||
}
|
||||
for (var domain in directDomains) {
|
||||
if (host.endsWith('.' + domain)) {
|
||||
return isLastNode
|
||||
}
|
||||
|
||||
function isInDirectDomain(host) {
|
||||
for (var i = 0; i < directDomains.length; i++) {
|
||||
var domain = directDomains[i];
|
||||
if (host === domain || host.endsWith('.' + domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -87,11 +90,9 @@ function isInDirectDomain(host) {
|
||||
}
|
||||
|
||||
function isInProxyDomain(host) {
|
||||
if (hasOwnProperty.call(domainsUsingProxy, host)) {
|
||||
return true;
|
||||
}
|
||||
for (var domain in domainsUsingProxy) {
|
||||
if (host.endsWith('.' + domain)) {
|
||||
for (var i = 0; i < domainsUsingProxy.length; i++) {
|
||||
var domain = domainsUsingProxy[i];
|
||||
if (host === domain || host.endsWith('.' + domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +105,9 @@ function isLocalTestDomain(domain) {
|
||||
if (tld === domain) {
|
||||
return false;
|
||||
}
|
||||
return Object.hasOwnProperty.call(localTlds, tld);
|
||||
return localTlds.some(function(localTld) {
|
||||
return tld === localTld;
|
||||
});
|
||||
}
|
||||
|
||||
/* https://github.com/frenchbread/private-ip */
|
||||
@@ -121,37 +124,65 @@ function isPrivateIp(ip) {
|
||||
}
|
||||
|
||||
function FindProxyForURL(url, host) {
|
||||
if (isPlainHostName(host)
|
||||
|| isPrivateIp(host)
|
||||
|| isLocalTestDomain(host)
|
||||
|| host === 'localhost') {
|
||||
if (isInDirectDomain(host)) {
|
||||
debug('命中直连域名', host, 'N/A');
|
||||
return direct;
|
||||
}
|
||||
|
||||
if (shExpMatch(url, "http:*")) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
if (!ipRegExp.test(host)) {
|
||||
if (isInDirectDomain(host)) {
|
||||
return direct
|
||||
}
|
||||
if (isInProxyDomain(host)) {
|
||||
return proxy;
|
||||
}
|
||||
strIp = dnsResolve(host);
|
||||
} else {
|
||||
strIp = host
|
||||
}
|
||||
|
||||
if (!strIp) {
|
||||
} else if (isInProxyDomain(host)) {
|
||||
debug('命中代理域名', host, 'N/A');
|
||||
return proxy;
|
||||
}
|
||||
|
||||
intIp = convertAddress(strIp);
|
||||
if (match(intIp)) {
|
||||
} else if (isPlainHostName(host) || host === 'localhost' || isLocalTestDomain(host)) {
|
||||
debug('命中本地主机名或本地tld', host, 'N/A');
|
||||
return direct;
|
||||
} else if (isPrivateIp(host)) {
|
||||
debug('命中私有 IP 地址', host, 'N/A');
|
||||
return direct;
|
||||
}
|
||||
|
||||
ip = isIpAddress(host) ? host : dnsResolve(host);
|
||||
|
||||
if (!ip) {
|
||||
debug('无法解析 IP 地址', host, 'N/A');
|
||||
return proxy;
|
||||
} else if (isPrivateIp(ip)) {
|
||||
debug('域名解析后命中私有 IP 地址', host, ip);
|
||||
return direct;
|
||||
} else if (searchRadixTree(ipToBinary(ip))) {
|
||||
debug('匹配到直连IP', host, ip);
|
||||
return direct;
|
||||
}
|
||||
|
||||
debug('未命中任何规则', host, ip);
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
var allowAlert = true
|
||||
function debug(msg, host, ip) {
|
||||
if (!allowAlert) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
alert('[' + host + ' -> ' + ip + '] ' + msg);
|
||||
} catch (e) {
|
||||
allowAlert = false
|
||||
}
|
||||
}
|
||||
|
||||
var radixTree = new RadixTree();
|
||||
|
||||
(function () {
|
||||
var startTime = new Date().getMilliseconds()
|
||||
debug('开始生成 Radix Tree', 'PAC文件载入开始', startTime.toString());
|
||||
for (let i=0; i<cidrs.length; i++) {
|
||||
var cidr = cidrs[i];
|
||||
var [ip, prefixLen] = cidr.split('/');
|
||||
if (!cidr.includes(':')) {
|
||||
var ip = ip.match(/.{1,2}/g).map(function(byte) {
|
||||
return parseInt(byte, 16);
|
||||
}).join('.');
|
||||
}
|
||||
var bits = ipToBinary(ip).slice(0, prefixLen);
|
||||
radixTree.insert(bits);
|
||||
}
|
||||
radixTree = radixTree.to_list();
|
||||
debug('Radix Tree 已生成', 'PAC文件载入完毕', cidrs.length.toString()+'个CIDR条目');
|
||||
})();
|
||||
Reference in New Issue
Block a user