UNPKG

shadowsocks-single

Version:

Yet another shadowsocks implementation for nodejs to help you bypass firewalls.

159 lines (125 loc) 4.01 kB
// NOTE: do not use these in local server import { join } from 'path'; import { request } from 'https'; import { request as httpRequest } from 'http'; import { parse } from 'url'; import { writeFile, readFileSync } from 'fs'; import { minify } from 'uglify-js'; export const GFWLIST_FILE_PATH = join(__dirname, '../pac/gfwlist.txt'); const DEFAULT_CONFIG = { localAddr: '127.0.0.1', localPort: '1080', }; const TARGET_URL = 'https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt'; const LINE_DELIMER = ['\r\n', '\r', '\n']; const MINIFY_OPTIONS = { fromString: true, }; let readLineLastContent = null; let readLineLastIndex = 0; function clear() { readLineLastContent = null; readLineLastIndex = 0; } export function readLine(text, shouldStrip) { let startIndex = 0; let i = null; let delimer = null; if (text === readLineLastContent) { startIndex = readLineLastIndex; } else { readLineLastContent = text; } LINE_DELIMER.forEach((char) => { const index = text.indexOf(char, startIndex); if (index !== -1 && (i === null || index < i)) { i = index; delimer = char; } }); if (i !== null) { readLineLastIndex = i + delimer.length; return shouldStrip ? text.slice(startIndex, i) : text.slice(startIndex, readLineLastIndex); } readLineLastIndex = 0; return null; } readLine.clear = clear; function shouldDropLine(line) { // NOTE: It's possible that gfwlist has rules that is a too long // regexp that may crush proxies like 'SwitchySharp' so we would // drop these rules here. return !line || line[0] === '!' || line[0] === '[' || line.length > 100; } const slashReg = /\//g; function encode(line) { return line.replace(slashReg, '\\/'); } export function createListArrayString(text) { const list = []; let line = readLine(text, true); while (line !== null) { if (!shouldDropLine(line)) { list.push(`"${encode(line)}"`); } line = readLine(text, true); } return `var rules = [${list.join(',\n')}];`; } export function createPACFileContent(text, { localAddr, localPort }) { const HOST = `${localAddr}:${localPort}`; const readFileOptions = { encoding: 'utf8' }; const userRulesString = readFileSync(join(__dirname, '../pac/user.txt'), readFileOptions); const rulesString = createListArrayString(`${userRulesString}\n${text}`); const SOCKS_STR = `var proxy = "SOCKS5 ${HOST}; SOCKS ${HOST}; DIRECT;";`; const matcherString = readFileSync(join(__dirname, '../vendor/ADPMatcher.js'), readFileOptions); return `${SOCKS_STR}\n${rulesString}\n${matcherString}`; } export function requestGFWList(targetURL, next) { const options = parse(targetURL); const requestMethod = (options.protocol.indexOf('https') >= 0 ? request : httpRequest); const req = requestMethod(options, (res) => { let data = null; res.on('data', (chunk) => { data = data ? Buffer.concat([data, chunk]) : chunk; }); res.on('end', () => { // gfwlist.txt use utf8 encoded content to present base64 content const listText = Buffer.from(data.toString(), 'base64'); next(null, listText); }); }); req.on('error', (err) => { next(err); }); req.end(); } function minifyCode(code) { return minify(code, MINIFY_OPTIONS).code; } // TODO: async this export function getPACFileContent(_config) { const config = _config || DEFAULT_CONFIG; const listText = readFileSync(GFWLIST_FILE_PATH, { encoding: 'utf8' }); return minifyCode(createPACFileContent(listText, config)); } function writeGFWList(listBuffer, next) { writeFile(GFWLIST_FILE_PATH, listBuffer, next); } export function updateGFWList(...args) { let targetURL = TARGET_URL; let next; if (args.length === 1) { next = args[0]; } else if (args.length === 2) { targetURL = args[0]; next = args[1]; } requestGFWList(targetURL, (err, listBuffer) => { if (err) { next(err); return; } writeGFWList(listBuffer, next); }); }