runet-censorship-bypass/generate-PACs.js

415 lines
9.8 KiB
JavaScript
Raw Normal View History

2015-11-27 23:47:27 +03:00
var fs = require('fs');
var input = fs.readFileSync('dump.csv').toString();
var outputDir = 'generated-PACs';
try {
fs.mkdirSync( outputDir );
} catch(e) {
if ( e.code != 'EEXIST' ) throw e;
}
var punycode = require('punycode')
/*
CVS Format:
IP(s);host(s);URL(s);organization(s);reason;yyyy-mm-dd
* multiple values are joind by " | "
* url may have wierd protocol, e.g.: Newcamd525://
*/
var columnsSep = ';';
var valuesSep = /\s*\|\s*/g;
var ips = [], hosts = [], urls = [], orgs = [], date, reason;
for(var line of input.trim().split(/\r?\n/g).slice(1)) {
var values = line.split( columnsSep );
var newIps = values.shift().split( valuesSep );
var newHosts = values.shift().split( valuesSep ).map( punycode.toASCII ).map( host => host.replace(/\.+$/g) );
var newUrls = values.shift().split( valuesSep );
var newOrgs = values.shift().split( valuesSep );
var newDate = values.pop();
var newReason = values.join(';');
ips.push.apply(ips, newIps);
hosts.push.apply(hosts, newHosts);
}
function toHash(arr) {
var res = {};
arr.forEach( el => res[el] = true );
return res;
}
var ipsHash = toHash(ips);
var hostsHash = toHash(hosts);
// Remove duplicates and sort.
var ips = Object.keys(ipsHash).sort();
var hosts = Object.keys(hostsHash).sort();
function FindProxyForURL(url, host) {
// ProstoVPN.AntiZapret PAC-ip File
// Generated on Sun Nov 22 10:12:29 MSK 2015
// The whole PAC script is reevaluated on each call of this function.
host = host.replace(/\.+$/).toLowerCase(); // E.g. WinHTTP may be nasty.
// HTTPS proxy is a HTTP proxy over SSL. It is NOT CONNECT proxy!
// Supported only in Chrome and Firefox.
// http://www.chromium.org/developers/design-documents/secure-web-proxy
// This is to bypass FULL DPI
var isIE = /*@cc_on!@*/!1;
var viaProxy = isIE
? 'PROXY proxy.antizapret.prostovpn.org:3128; DIRECT'
: 'HTTPS proxy.antizapret.prostovpn.org:3143; PROXY proxy.antizapret.prostovpn.org:3128; DIRECT';
return IFPROXY() ? viaProxy : 'DIRECT';
}
var pacTemplate = FindProxyForURL.toString();
function stringifyCall() {
var fun = arguments[0];
var args = [].slice.call( arguments, 1 )
.map( a => typeof a !== 'string' ? JSON.stringify(a) : a ).join(', ');
return '('+fun+')('+args+')';
}
function produceOutput() {
var args = [].slice.call( arguments )
var scriptName = args.shift();
var script = pacTemplate
.replace( 'IFPROXY()', stringifyCall.apply(this, args) );
fs.writeFileSync(outputDir +'/'+ scriptName +'.js', script);
}
// BLOCKED IPS ARRAY
function ifProxyByIp(host, blockedIpsArray) {
// Internet Explorer
if (!Array.prototype.indexOf)
Array.prototype.indexOf = function(obj, start) {
for (var i = (start || 0), j = this.length; i < j; i++)
if (this[i] === obj) return i;
return -1;
}
return blockedIpsArray.indexOf( dnsResolve(host) ) !== -1
}
produceOutput('blocked-ips-indexOf', ifProxyByIp, 'host', ips);
// BLOCKED IPS SWITCH
function ifProxyBySwitch(host) {
switch( dnsResolve(host) ) {
/*{CASES}*/
default:
return false;
}
}
var cases = ips.map( ip => 'case "'+ip+'":' ).join('\n') +'\nreturn true;';
var ifProxySwitchStr = ifProxyBySwitch.toString().replace('/*{CASES}*/', cases);
produceOutput('blocked-ips-switch', ifProxySwitchStr, 'host' );
// BLOCKED IPS BINARY
function ifBinaryFound(target, sortedArray) {
var istart = 0;
var iend = sortedArray.length - 1;
while (istart < iend) {
var imid = istart + Math.floor( (iend - istart)*0.5 );
if (target > sortedArray[imid])
istart = imid + 1;
else
iend = imid;
}
return target === sortedArray[iend];
}
produceOutput('blocked-ips-binary', ifBinaryFound, 'dnsResolve(host)', ips);
// BLOCKED HOSTS BINARY
produceOutput('blocked-hosts-binary', ifBinaryFound, 'host', hosts);
// REVERSED BINARY
var reverse = str => str.split('').reverse().join('');
var reversedHosts = hosts.map( reverse ).sort();
function ifReversedBinaryFound(host, sortedArray) {
target = host.split('').reverse().join('');
var istart = 0;
var iend = sortedArray.length - 1;
while (istart < iend) {
var imid = istart + Math.floor( (iend - istart)*0.5 );
if (target > sortedArray[imid])
istart = imid + 1;
else
iend = imid;
}
return dnsDomainIs( host, sortedArray[iend].split('').reverse().join('') );
}
produceOutput('blocked-hosts-reversed-binary', ifReversedBinaryFound, 'host', reversedHosts);
// REVERSED HOSTS SWITCH
function populateTrie(trie, doms) {
var dom = doms.pop();
if (!doms.length || doms.length === 1 && doms[0] === 'www') {
trie[''] = trie[''] || [];
trie[''].push( dom )
return trie;
}
if (trie[''] && trie[''].indexOf(dom) !== -1) // Subdomain of a blocked domain.
return trie;
trie[dom] = trie[dom] || {};
populateTrie( trie[dom], doms );
return trie;
}
var trie = {};
for(var host of hosts) {
var doms = host.split('.');
populateTrie(trie, doms);
}
function trieToSwitch(trie, indent) {
var _indent = indent || '';
var indent = _indent + ' ';
var keys = Object.keys(trie).sort();
if (!trie[''] && keys.length === 1) {
var key = keys[0];
return _indent + 'if (doms.pop() === "'+key+'")\n'+ trieToSwitch(trie[key], indent);
}
var cases = '';
if (trie['']) {
var values = trie[''].sort();
if (values.length === 1 && keys.length === 1)
return _indent + 'return doms.pop() === "'+values[0]+'";\n';
cases =
values.filter( v => v ).map( val => indent +'case "'+val+'":\n' ).join('') + indent +' return true;\n';
delete trie[''];
keys = Object.keys(trie).sort();
}
cases += keys.filter( k => k ).map(
key => {
var tmp = trieToSwitch( trie[key], indent+' ');
if (!/^\s*return/.test(tmp))
tmp += indent+' break;\n';
return indent +'case "'+key+'":\n' +tmp;
}
).join('');
return ''
+ _indent +'switch( doms.pop() ) {\n'
+ cases
+ _indent +'}\n';
}
function ifProxyByTrie(host) {
//SWITCH
return false;
}
var ifProxyByTrieStr = ifProxyByTrie.toString().replace('//SWITCH', trieToSwitch(trie, ' '));
produceOutput('blocked-hosts-switch', ifProxyByTrieStr, 'host' );
/* REVERSED HOSTS BINARY TRIE
function populateBinTrie(trie, doms) {
var dom = doms.pop();
if (!doms.length || doms.length === 1 && doms[0] === 'www') {
trie[dom] = 'blocked';
return trie;
}
if (trie[dom] === 'blocked') // Subdomain of a blocked domain.
return trie;
trie[dom] = trie[dom] || {};
populateBinTrie( trie[dom], doms );
return trie;
}
var trie = {};
for(var host of hosts) {
var doms = host.split('.');
populateBinTrie(trie, doms);
}
function trie2sorted(trie) {
if (trie === 'blocked')
return trie;
var keys = Object.keys(trie).sort();
return keys.map( key => trie[key] !== 'blocked' ? [ key, trie2sorted( trie[key] ) ] : [key] );
}
var sortedTrie = trie2sorted(trie);
function ifProxyByBinTrie(host, sortedTrie) {
var doms = host.split('.');
function ifBinaryBlocked(sortedTrie) {
var target = doms.pop();
if (!target)
return false;
var istart = 0;
var iend = sortedTrie.length - 1;
while (istart < iend) {
var imid = istart + Math.floor( (iend - istart)*0.5 );
if (target > sortedTrie[imid][0])
istart = imid + 1;
else
iend = imid;
}
if (target !== sortedTrie[iend][0])
return false;
return sortedTrie[iend].length < 2 ? true : ifBinaryBlocked( sortedTrie[iend][1] );
}
return ifBinaryBlocked(sortedTrie)
}
produceOutput('blocked-hosts-binary-trie', ifProxyByBinTrie, 'host', sortedTrie);
*/
// BLOCKED HOSTS PLAIN SWITCH
function ifProxyByPlainSwitch(host) {
function ifBlocked(host) {
switch( host ) {
//CASES
}
return false;
}
var doms = host.split('.');
2015-11-28 03:32:25 +03:00
for( var endi = doms.length-1; endi >= 0; --endi )
2015-11-27 23:47:27 +03:00
if (ifBlocked( doms.slice( endi ).join('.') ))
return true;
return false;
}
var cases = hosts.map( host => 'case "'+host+'":' ).join('\n') +'\nreturn true;';
var tmp = ifProxyByPlainSwitch.toString().replace('//CASES', cases);
2015-11-28 03:32:25 +03:00
produceOutput( 'blocked-hosts-plain-switch', tmp, 'host' );
// SIMPLY TRIE
function populateSimpleTrie(trie, letters) {
if (!letters.length || !trie)
return trie;
var letter = letters.shift();
if (!letters.length) {
trie[letter] = ['', false];
return trie;
}
if (trie[letter] === ['', false]) // Subdomain of a blocked domain.
return trie;
if (!trie[letter]) {
trie[letter] = [letters.join(''), false]
return trie;
}
var arr = trie[letter];
var link = arr[0].split('');
var linkTrie = arr[1];
function commonPrefix(wa, wb) {
var len = Math.min(wa.length, wb.length);
var i = -1;
while(++i < len)
if (wa[i] !== wb[i])
break;
return wa.slice(0, i)
}
var prefix = commonPrefix(link, letters);
var suffixLetters = letters.slice(prefix.length);
var suffixLink = link.slice(prefix.length);
var newTrie = {};
if (!suffixLink.length)
var newTrie = linkTrie;
if (!suffixLetters.length)
var newTrie = false;
if (newTrie) {
newTrie = populateSimpleTrie( newTrie, suffixLetters );
newTrie = populateSimpleTrie( newTrie, suffixLink );
}
trie[letter] = [prefix.join(''), newTrie];
return trie;
}
var simpleTrie = {};
for(var host of hosts)
populateSimpleTrie(simpleTrie, host.split('').reverse());
function ifProxyBySimpleTrie(host, simpleTrie) {
var letters = host.split('').reverse();
while(letters.length) {
var letter = letters.shift();
var arr = simpleTrie[letter];
var link = arr[0];
if (link.length > letters.length)
return false;
for( var i=0; i < link.length; ++i )
if ( letters[i] !== link.charAt(i) )
return false;
letters = letters.slice(link.length);
simpleTrie = arr[1];
if (!simpleTrie)
return true;
}
}
produceOutput('blocked-hosts-simple-trie', ifProxyBySimpleTrie, 'host', simpleTrie);