whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
1,195 lines (1,129 loc) • 38 kB
JavaScript
var path = require('path');
var http = require('http');
var fs = require('fs');
var net = require('net');
var dns = require('dns');
var qs = require('querystring');
var LRU = require('lru-cache');
var httpAgent = http.Agent;
var httpsAgent = require('https').Agent;
var parseUrl = require('./util/parse-url-safe');
var fse = require('fs-extra2');
var extend = require('extend');
var tunnel = require('hagent').agent;
var socks = require('sockx');
var json5 = require('json5');
var pkgConf = require('../package.json');
var config = extend(exports, pkgConf);
var common = require('./util/common');
var customUIHost;
var customHostPluginMap = {};
var customPluginNameHost = {};
var WHISTLE_PLUGIN_RE = /^(?:whistle\.)?([a-z\d_\-]+)$/;
var CMD_RE = /^[\w]{1,12}(?:\s+-g)?$/;
var httpsAgents = new LRU({ max: 360 });
var socksAgents = new LRU({ max: 100 });
var version = process.version.substring(1).split('.');
var disableAgent = version[0] > 10;
var useVerbatim = version[0] >= 17;
var hasIPv6First = version[0] >= 22 && version[1] >= 1;
var hasDnsOrder = typeof dns.setDefaultResultOrder === 'function';
var uid = Date.now().toString(16) + (process.pid * 10000 + Math.floor(Math.random() * 10000)).toString(16);
var IPV4_RE = /^([\d.]+)(:\d{1,5})?$/;
var IPV6_RE = /^\[([\w:]+)\](:\d{1,5})?$/;
var noop = function () {};
var DATA_KEY_RE = /^(clientip|clientid|tunneldata)([=.])([\w.-]+)$/i;
var DNS_ORDERS = ['ipv4First', 'ipv4first', 'ipv6first', 'ipv6First', 'verbatim'];
var LOCAL_UI_HOST_LIST = [
'local.whistlejs.com',
'local.wproxy.org',
'rootca.pro'
];
var variableProperties = [
'encrypted',
'sockets',
'dataDirname',
'storage',
'baseDir',
'noGlobalPlugins',
'pluginsDataMap',
'globalData',
'username',
'password',
'debugMode',
'localUIHost',
'extra',
'rules',
'values',
'dnsCache',
'allowDisableShadowRules',
'disableInstaller'
];
if (hasDnsOrder) {
if (hasIPv6First) {
config.verbatim = 2;
} else {
config.verbatim = 1;
}
}
config.appName = common.upperFirst(config.name);
config.defaultDnsOrder = useVerbatim ? 1 : 2;
config.uid = uid;
config.rejectUnauthorized = false;
config.enableH2 = version[0] > 12; // 支持 HTTP2 要求的最低 Node 版本
config.ASSESTS_PATH = path.join(__dirname, '../assets');
config.WHISTLE_POLICY_HEADER = 'x-whistle-policy';
config.CLIENT_IP_HEADER = common.CLIENT_IP_HEADER;
config.HTTPS_FIELD = common.HTTPS_FIELD;
config.CLIENT_INFO_HEADER = 'x-whistle-client-info-' + uid;
config.SHOW_LOGIN_BOX = 'x-whistle-show-login-box-' + uid;
config.WEBUI_PATH = '/.whistle-path.5b6af7b9884e1165/';
config.PREVIEW_PATH_RE = /\?\?\?WHISTLE_PREVIEW_CHARSET=([A-Z\d_-]+)\?\?\?$/;
config.PLUGIN_HOOK_NAME_HEADER = 'x-whistle-plugin-hook-name_';
config.CLIENT_ID_HEADER = common.CLIENT_ID_HEADER;
config.CLIENT_PORT_HEADER = common.CLIENT_PORT_HEADER;
config.ALPN_PROTOCOL_HEADER = common.ALPN_PROTOCOL_HEADER;
config.DISABLE_RULES_HEADER = 'x-whistle-proxy-rules-' + uid;
config.PROXY_ID_HEADER = 'x-whistle-proxy-id-' + uid;
config.WEBUI_HEAD = 'x-forwarded-from-whistle-' + uid;
config.RES_RULES_HEAD = 'x-whistle-res-rules-' + uid;
config.COMPOSER_CLIENT_ID_HEADER = 'x-whistle-client-id-' + uid;
config.FROM_COM_HEADER = 'x-whistle-composer-' + uid;
config.PLUGIN_HOOKS = {
SNI: 'sni-' + uid,
AUTH: 'auth-' + uid,
HTTP: 'http-' + uid,
UI: 'ui-' + uid,
TUNNEL: 'tunnel-' + uid,
TUNNEL_RULES: 'tunnel-rules-' + uid,
REQ_STATS: 'req-stats-' + uid,
RES_STATS: 'res-stats-' + uid,
REQ_RULES: 'req-rules-' + uid,
RES_RULES: 'res-rules-' + uid,
REQ_READ: 'req-read-' + uid,
REQ_WRITE: 'req-write-' + uid,
RES_READ: 'res-read-' + uid,
RES_WRITE: 'res-write-' + uid,
WS_REQ_READ: 'ws-req-read-' + uid,
WS_REQ_WRITE: 'ws-req-write-' + uid,
WS_RES_READ: 'ws-res-read-' + uid,
WS_RES_WRITE: 'ws-res-write-' + uid,
TUNNEL_REQ_READ: 'tunnel-req-read-' + uid,
TUNNEL_REQ_WRITE: 'tunnel-req-write-' + uid,
TUNNEL_RES_READ: 'tunnel-res-read-' + uid,
TUNNEL_RES_WRITE: 'tunnel-res-write-' + uid
};
var KEEP_ALIVE_MSECS = 10000;
var CONN_TIMEOUT = common.CONN_TIMEOUT;
var DNS_ORDERS_OPTS = ['', 'verbatim', 'ipv4first', hasIPv6First ? 'ipv6first' : ''];
var DOMAIN_STAR_RE = /([*~]+)(\\.)?/g;
config.CONN_TIMEOUT = CONN_TIMEOUT;
var getWhistlePath = common.getWhistlePath;
var getHostPort = common.getHostPort;
var ALL_WHISTLES_PATH = path.join(getWhistlePath(), 'all_whistles');
function domainToRegExp(_, star, dot) {
var len = star.length;
var result = len > 1 ? '([^/?]*)' : '([^/?.]*)';
if (dot) {
result += '\\.';
if (len > 2) {
result = '(?:' + result + ')?';
}
}
return result;
}
function getDataDir(dirname) {
var dir = path.join(getWhistlePath(), dirname || '.' + config.name);
fse.ensureDirSync(dir);
return dir;
}
config.baseDir = getDataDir();
config.TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
config.CUSTOM_PLUGIN_PATH = path.join(getWhistlePath(), 'custom_plugins');
config.CUSTOM_CERTS_DIR = path.resolve(getWhistlePath(), 'custom_certs');
config.SAVED_SESSIONS_PATH = path.resolve(getWhistlePath(), 'saved_sessions');
config.DEV_PLUGINS_PATH = path.resolve(getWhistlePath(), 'dev_plugins');
config.rulesDir = path.join(config.baseDir, 'rules');
config.valuesDir = path.join(config.baseDir, 'values');
config.propertiesDir = path.join(config.baseDir, 'properties');
try {
fse.ensureDirSync(config.TEMP_FILES_PATH);
var blankFile = path.join(config.TEMP_FILES_PATH, 'blank');
if (!fs.existsSync(blankFile)) {
fs.writeFileSync(blankFile, '');
}
} catch (e) {}
try {
fse.ensureDirSync(config.CUSTOM_CERTS_DIR);
} catch (e) {}
try {
fse.ensureDirSync(config.SAVED_SESSIONS_PATH);
} catch (e) {}
exports.getWhistlePath = getWhistlePath;
exports.getDataDir = getDataDir;
var async_id_symbol;
if (version[0] < 16) {
try {
async_id_symbol = process.binding('async_wrap').async_id_symbol;
} catch (e) {}
}
var emptyHandle = {
asyncReset: noop,
getAsyncId: noop
};
function createAgent(agentConfig, https) {
var agent = new (https ? httpsAgent : httpAgent)(agentConfig);
if (async_id_symbol) {
var addRequest = agent.addRequest;
agent.addRequest = function (req, options) {
// fix: https://github.com/nodejs/node/issues/13539
var freeSockets = this.freeSockets[this.getName(options)];
if (freeSockets && freeSockets.length) {
var socket = freeSockets[0];
var handle = socket._handle;
if (!handle) {
socket._handle = emptyHandle;
} else if (typeof handle.asyncReset !== 'function') {
handle.asyncReset = noop;
}
var originalRef = socket.ref;
socket.ref = function () {
socket.ref = originalRef;
if (socket._handle === emptyHandle) {
delete socket._handle;
} else if (socket._handle.asyncReset === noop) {
delete socket._handle.asyncReset;
}
socket.ref();
};
}
var onSocket = req.onSocket;
req.onSocket = function (socket) {
try {
socket[async_id_symbol] = socket._handle.getAsyncId();
} catch (e) {}
onSocket.apply(this, arguments);
};
addRequest.apply(this, arguments);
};
}
return agent;
}
function capitalize(str) {
return (str && str[0].toUpperCase()) + str.substring(1);
}
function getHttpsAgent(options, reqOpts) {
var key = getCacheKey(options);
var agent = httpsAgents.get(key);
if (reqOpts) {
var headers = options.headers || {};
var proxyHeaders = {};
Object.keys(headers).forEach(function (key) {
var rawKey = key.split('-').map(capitalize).join('-');
proxyHeaders[rawKey] = headers[key];
});
reqOpts._tunnelProxyHeaders = proxyHeaders;
}
if (!agent) {
options.proxyAuth = options.auth;
options.rejectUnauthorized = config.rejectUnauthorized;
options.proxy = {
host: options.proxyHost,
port: options.proxyPort,
enableIntercept: options.enableIntercept,
keepStreamResume: options.keepStreamResume,
proxyTunnelPath: options.proxyTunnelPath
};
var agentName = options.isHttps ? 'httpsOverHttp' : 'httpOverHttp';
if (options.proxyServername) {
options.proxy.servername = options.proxyServername;
agentName += 's';
}
agent = new tunnel[agentName](options);
httpsAgents.set(key, agent);
agent.on('free', preventThrowOutError);
}
return agent;
}
exports.getHttpsAgent = getHttpsAgent;
function getCacheKey(options, isSocks) {
var auth = options.auth || options.proxyAuth;
if (!auth) {
var headers = options.headers || '';
auth = headers['proxy-authorization'] || '';
}
var ip = (!isSocks && options.clientIp) || '';
return [
options.proxyServername ? 'https-proxy' : '',
options.isHttps ? 'https' : 'http',
options.proxyHost,
options.proxyPort,
auth,
ip,
options.proxyTunnelPath || '',
(options.isHttps && options.cacheKey) || ''
].join(':');
}
function getAuths(_url) {
var options = typeof _url == 'string' ? parseUrl(_url) : _url;
if (!options || !options.auth) {
return [socks.auth.None()];
}
var auths = [];
options.auth.split('|').forEach(function (auth) {
auth = auth.trim();
if (auth) {
var index = auth.indexOf(':');
auths.push({
username: index == -1 ? auth : auth.substring(0, index),
password: index == -1 ? '' : auth.substring(index + 1)
});
}
});
return auths.length
? auths.map(function (auth) {
return socks.auth.UserPassword(auth.username, auth.password);
})
: [socks.auth.None()];
}
exports.getAuths = getAuths;
function setDefaultResultOrder(order) {
if (!hasDnsOrder) {
return;
}
order = DNS_ORDERS_OPTS.indexOf(order) === -1 ? DNS_ORDERS_OPTS[order] : order;
if (!order) {
return;
}
config.ipv4First = false;
config.ipv6First = false;
if (order[3] === '6') {
config.ipv6First = true;
} else if (order[3] === '4') {
config.ipv4First = true;
}
try {
if (dns.getDefaultResultOrder() !== order) {
dns.setDefaultResultOrder(order);
}
config.dnsOrder = DNS_ORDERS_OPTS.indexOf(order);
return true;
} catch (e) {}
}
exports.setDefaultResultOrder = setDefaultResultOrder;
exports.setAuth = function (auth) {
if (auth) {
config.username = getString(auth.username);
var password = (config.password = getString(auth.password));
config.passwordHash = password && common.createHash(password);
setGuestAuth(auth);
}
return config;
};
exports.getPluginData = function (name) {
var pluginsDataMap = name && config.pluginsDataMap;
if (!pluginsDataMap) {
return;
}
name = name.substring(name.lastIndexOf('.') + 1);
return pluginsDataMap[name];
};
exports.setGuestAuth = function (auth) {
auth && setGuestAuth(auth);
};
function parseHost(host) {
if (Array.isArray(host)) {
return host.filter(function (h) {
return h && typeof h === 'string';
});
}
host = typeof host === 'string' ? host.trim() : '';
return host && host.split('|').map(getHostname);
}
exports.setUIHost = function (host) {
customUIHost = parseHost(host);
};
exports.setPluginUIHost = function (pluginName, host) {
if (!pluginName || !WHISTLE_PLUGIN_RE.test(pluginName)) {
return;
}
pluginName = RegExp.$1;
host = parseHost(host);
if (host) {
customPluginNameHost[pluginName] = host;
host.forEach(function (h) {
delete customHostPluginMap[h];
});
} else {
delete customPluginNameHost[pluginName];
}
customHostPluginMap = {};
Object.keys(customPluginNameHost).forEach(function (name) {
var list = customPluginNameHost[name];
list &&
list.forEach(function (h) {
customHostPluginMap[h] = name;
});
});
};
exports.connect = function (options, cb) {
return common.connectInner(options, cb, config);
};
function preventThrowOutError(socket) {
if (socket.listeners('error').indexOf(freeSocketErrorListener) === -1) {
socket.once('error', freeSocketErrorListener);
}
}
function freeSocketErrorListener() {
var socket = this;
socket.destroy();
socket.emit('agentRemove');
socket.removeListener('error', freeSocketErrorListener);
}
function resolvePath(file) {
if (!file || !(file = file.trim())) {
return file;
}
return /^[\w-]+$/.test(file) ? file : path.resolve(file);
}
function getHostname(_url) {
if (typeof _url != 'string') {
return '';
}
if (_url.indexOf('/') != -1) {
return parseUrl(_url).hostname;
}
var index = _url.indexOf(':');
return index == -1 ? _url : _url.substring(0, index);
}
function getPaths(paths, isCustom) {
if (typeof paths === 'string') {
paths = paths.trim().split(/\s*[|,;]\s*/);
} else if (!Array.isArray(paths)) {
return;
}
var result = [];
paths.forEach(function (path) {
if (isCustom && ['self', 'buildin', 'buildIn', 'build-in'].indexOf(path) !== -1) {
result.push(config.CUSTOM_PLUGIN_PATH);
} else if (path && typeof path === 'string') {
result.push(path);
}
});
return result.length ? result : null;
}
function getSecureFilter(newConf) {
var secureFilter = newConf.secureFilter;
if (typeof secureFilter === 'function') {
return secureFilter;
}
if (!secureFilter || typeof secureFilter !== 'string') {
return;
}
try {
return require(secureFilter);
} catch (e) {}
}
function getString(str) {
return str && typeof str === 'string' ? str : '';
}
function readFileText(filepath) {
var text = common.readFileTextSync(filepath);
return text && text.trim();
}
function readFileBuffer(filepath, required) {
return common.readFileBufferSync(filepath, required === false);
}
function isPort(port) {
return /^\d+$/.test(port) && port <= 65535 && port > 0;
}
function setGuestAuth(auth) {
if (auth.guestName === '-' && !auth.guestPassword) {
config.guest = {};
} else if (auth.guestName && auth.guestPassword) {
config.guest = {
username: auth.guestName,
password: auth.guestPassword
};
} else if ('guest' in auth) {
config.guest = auth.guest;
}
}
function parseString(str) {
str = str && typeof str === 'string' && str.trim();
if (!str) {
return '';
}
str = readFileText(str) || str;
if (!/\s/.test(str) && str.indexOf('%') !== -1) {
try {
str = decodeURIComponent(str);
} catch (e) {}
}
if (/^\{[\s\S]+\}$/.test(str) || /^\[[\s\S]+\]$/.test(str)) {
try {
return json5.parse(str);
} catch (e) {}
}
return str;
}
function getPluginList(list) {
if (typeof list == 'string') {
list = list.trim().split(/\s*[,|]\s*/);
}
var result;
if (Array.isArray(list)) {
list.forEach(function (name) {
if (WHISTLE_PLUGIN_RE.test(name)) {
result = result || [];
result.push(RegExp.$1);
}
});
}
return result;
}
function readUIExt(uiExt) {
if (!uiExt) {
return;
}
var htmlPrepend = uiExt.htmlPrepend && readFileBuffer(uiExt.htmlPrepend, uiExt.required);
var htmlAppend = uiExt.htmlAppend && readFileBuffer(uiExt.htmlAppend, uiExt.required);
var jsPrepend = uiExt.jsPrepend && readFileBuffer(uiExt.jsPrepend, uiExt.required);
var jsAppend = uiExt.jsAppend && readFileBuffer(uiExt.jsAppend, uiExt.required);
if (htmlPrepend ||htmlAppend || jsPrepend || jsAppend) {
if (htmlPrepend) {
htmlPrepend = Buffer.concat([Buffer.from('<!DOCTYPE html>\n'), htmlPrepend]);
}
return {
htmlPrepend: htmlPrepend,
htmlAppend: htmlAppend,
jsPrepend: jsPrepend,
jsAppend: jsAppend
};
}
}
exports.extend = function (newConf) {
config.pluginHostMap = {};
config.uiport = config.port;
config.ipv6Only = false;
var rcConf = common.readWhistleRc(newConf);
newConf = extend({}, rcConf, newConf);
if (rcConf) {
if (rcConf.clearPreOptions) {
newConf.clearPreOptions = true;
}
if (rcConf.noGlobalPlugins) {
newConf.noGlobalPlugins = true;
}
}
if (process.env.WHISTLE_MODE) {
newConf.mode = process.env.WHISTLE_MODE + '|' + (newConf.mode || '');
}
var useAgent;
var newAgentConf;
if (newConf.specialPath && typeof newConf.specialPath === 'string') {
config.SPEC_PATH = newConf.specialPath;
}
if (newConf.whistleName && common.isWhistleName(newConf.whistleName)) {
config.whistleName = newConf.whistleName;
}
config.uiExt = readUIExt(newConf.uiExt);
config.specialAuth = newConf.specialAuth;
config.uiMiddleware = newConf.uiMiddlewares || newConf.uiMiddleware;
newAgentConf = newConf.agentConfig;
if (newConf.cmdName && CMD_RE.test(newConf.cmdName)) {
config.cmdName = newConf.cmdName;
}
if (newConf.account && typeof newConf.account === 'string') {
config.account = newConf.account;
}
config.allowPluginList = getPluginList(newConf.allowPluginList);
config.blockPluginList = getPluginList(newConf.blockPluginList);
if (newConf.webUIPath && /^[\w.-]+$/.test(newConf.webUIPath)) {
config.WEBUI_PATH = '/.' + newConf.webUIPath + config.WEBUI_PATH.substring(1);
}
if (newConf.cluster) {
config.headless = true;
config.workerIndex = process.env.workerIndex;
if (typeof newConf.mode !== 'string') {
newConf.mode = '';
}
}
var dnsServer = newConf.dnsServer;
var resolve6;
var dnsOptional;
if (common.isString(dnsServer)) {
dnsServer = dnsServer.trim();
if (common.isUrl(dnsServer)) {
config.dnsOverHttps = dnsServer;
} else {
dnsServer = dnsServer.split(/[|,&]/);
}
}
var allowOrigin = newConf.allowOrigin;
if (common.isString(allowOrigin)) {
allowOrigin = allowOrigin.trim().toLowerCase().split(/\s*[|,&]\s*/);
if (allowOrigin.indexOf('*') === -1) {
allowOrigin = allowOrigin.map(function(h) {
if (h.indexOf('*') === -1) {
return h;
}
h = common.escapeRegExp(h);
h = h.replace(DOMAIN_STAR_RE, domainToRegExp);
try {
return new RegExp('^' + h + '$');
} catch (e) {}
}).filter(function(h) {
return h;
});
if (allowOrigin.length) {
config.allowOrigin = allowOrigin;
}
} else {
config.allowAllOrigin = true;
}
}
if (dns.getServers && Array.isArray(dnsServer)) {
var newServers = [];
dnsServer.forEach(function (ip) {
ip = typeof ip === 'string' && ip.trim();
if (/^ipv6$/i.test(ip)) {
resolve6 = true;
} else if (ip === 'optional' || ip === 'default' || ip === 'fallback') {
dnsOptional = true;
} else if (net.isIP(ip)) {
newServers.push(ip);
} else if (IPV4_RE.test(ip) || IPV6_RE.test(ip)) {
var dnsIp = RegExp.$1;
var dnsPort = RegExp.$2;
if (net.isIP(dnsIp) && (!dnsPort || isPort(dnsPort.substring(1)))) {
newServers.push(ip);
}
}
});
if (newServers.length) {
dns.setServers(newServers);
config.resolve6 = resolve6;
config.dnsOptional = dnsOptional;
config.dnsServer = newServers.join();
}
}
if (newConf.inspect || newConf.inspectBrk) {
config.inspectMode = true;
process.env.PFORK_MODE = 'inline';
}
variableProperties.forEach(function (name) {
config[name] = newConf[name] || pkgConf[name];
});
var shadowRules = parseString(newConf.shadowRules);
var resolveShadowValues = function (obj) {
if (
(!obj.rules || typeof obj.rules === 'string') &&
obj.values &&
typeof obj.values !== 'string'
) {
config.shadowRules = obj.rules || obj.rules.trim();
if (Object.keys(obj.values).length) {
config.shadowValues = obj.values;
}
} else {
config.rules = extend({}, newConf.rules, obj);
}
};
if (typeof shadowRules === 'string') {
config.shadowRules = shadowRules.trim();
} else if (Array.isArray(shadowRules)) {
if (typeof shadowRules[0] === 'string') {
config.shadowRules = shadowRules[0].trim();
if (shadowRules[1]) {
config.rules = extend({}, newConf.rules, shadowRules[1]);
}
if (shadowRules[2]) {
config.values = extend({}, newConf.values, shadowRules[2]);
}
} else if (shadowRules[0]) {
resolveShadowValues(shadowRules[0]);
if (shadowRules[1]) {
config.values = extend({}, newConf.values, shadowRules[1]);
}
} else if (shadowRules[1]) {
config.values = extend({}, newConf.values, shadowRules[1]);
}
} else if (shadowRules) {
resolveShadowValues(shadowRules);
}
var extra = newConf.extra;
if (common.isString(extra)) {
extra = /^\s*\{[\w\W]*\}\s*$/.test(extra) ? extra : readFileText(extra);
try {
extra = extra && JSON.parse(extra);
if (extra && typeof extra === 'object') {
config.pluginsDataMap = extend({}, config.pluginsDataMap, extra);
}
} catch (e) {}
extra = null;
}
var customHandler = newConf.customHandler || newConf.customHandle;
if (typeof customHandler === 'function') {
config.customHandler = customHandler;
}
if (isPort(newConf.realPort) && config.realPort != config.port) {
config.realPort = newConf.realPort;
}
var realHost = newConf.realHost;
if (typeof realHost === 'string' && (!realHost || /^[\w.-]+$/.test(newConf.realHost))) {
config.realHost = newConf.realHost;
}
var socksPort = getHostPort(newConf.socksPort);
if (socksPort) {
config.socksPort = socksPort.port;
config.socksHost = socksPort.host;
}
var httpPort = getHostPort(newConf.httpPort);
if (httpPort) {
config.httpPort = httpPort.port;
config.httpHost = httpPort.host;
}
var httpsPort = getHostPort(newConf.httpsPort);
if (httpsPort) {
config.httpsPort = httpsPort.port;
config.httpsHost = httpsPort.host;
}
if (common.isString(newConf.host)) {
config.defaultHost = newConf.host;
}
if (typeof newConf.authKey === 'string') {
config.authKey = newConf.authKey;
}
if (typeof newConf.guestAuthKey === 'string') {
config.guestAuthKey = newConf.guestAuthKey;
}
if (newConf.reqCacheSize > 0) {
config.reqCacheSize = newConf.reqCacheSize;
}
if (newConf.frameCacheSize > 0) {
config.frameCacheSize = newConf.frameCacheSize;
}
config.allowMultipleChoice = newConf.allowMultipleChoice;
if (common.isString(newConf.mode)) {
var mode = newConf.mode.trim().split(/\s*[|,&]\s*/);
var useResolve;
var dnsFallback;
if (mode.indexOf('admin') !== -1) {
var admin = 'proxyServer|master|x-forwarded-proto';
admin = config.debugMode ? admin : `${admin}|strict|rules|disableUpdateTips|proxifier|notAllowedDisablePlugins`;
mode = admin.split('|').concat(mode);
}
if (mode.indexOf('multiple') !== -1) {
mode = 'multiEnv|disableUpdateTips|keepXFF|x-forwarded-proto'.split('|').concat(mode);
}
mode.forEach(function (m) {
m = m.trim();
if (
/^(pureProxy|debug|ipv6Only|master|client|diagnose|disableAuthUI|captureData|headless|strict|proxyServer|encrypted|noGzip|disableUpdateTips|proxifier2?)$/.test(m)
) {
config[m] = true;
} else if(m === 'ipv6only') {
config.ipv6Only = true;
} else if (m === 'keepProxyUI') {
config.keepProxyUI = true;
} else if (m === 'agent') {
useAgent = true;
} else if (m === 'disableUIAuth') {
config.disableAuthUI = true;
} else if (m === 'showPluginReq') {
config.showPluginReq = config.debugMode;
} else if (m === 'disableCustomCerts') {
config.disableCustomCerts = true;
} else if (m === 'nohost' || m === 'multienv' || m === 'multiEnv') {
config[m] = true;
config.multiEnv = true;
} else if (DNS_ORDERS.indexOf(m) !== -1) {
setDefaultResultOrder(m.toLowerCase());
} else if (m === 'proxyOnly') {
config.pureProxy = true;
} else if (m === 'useMultipleRules' || m === 'enableMultipleRules') {
config.allowMultipleChoice = true;
} else if (m === 'disableMultipleRules') {
config.allowMultipleChoice = false;
} else if (
m === 'notAllowDisableRules' ||
m === 'notAllowedDisableRules'
) {
config.notAllowedDisableRules = true;
} else if (m === 'disableBackOption' || m === 'disabledBackOption') {
config.disabledBackOption = true;
} else if (
m === 'disableMultipleOption' ||
m === 'disabledMultipleOption'
) {
config.disabledMultipleOption = true;
} else if (
m === 'disableRulesOptions' ||
m === 'disabledRulesOptions'
) {
config.disabledRulesOptions = true;
config.disabledBackOption = true;
config.disabledMultipleOption = true;
config.notAllowedDisableRules = true;
} else if (
m === 'notAllowDisablePlugins' ||
m === 'notAllowedDisablePlugins'
) {
config.notAllowedDisablePlugins = true;
} else if (
m === 'notAllowEnableHTTPS' ||
m === 'notAllowedEnableHTTPS'
) {
config.notAllowedEnableHTTPS = true;
} else if (m === 'socks') {
config.socksMode = true;
} else if (m === 'network') {
config.networkMode = true;
config.captureData = true;
} else if (m === 'shadowRules') {
config.shadowRulesMode = true;
} else if (m === 'shadowRulesOnly') {
config.shadowRulesMode = true;
config.disableWebUI = true;
} else if (m === 'plugins') {
config.pluginsMode = true;
} else if (m === 'pluginsOnly') {
config.pluginsOnlyMode = true;
} else if (m === 'rules') {
config.rulesMode = true;
} else if (m === 'rulesOnly') {
config.rulesOnlyMode = true;
} else if (m === 'httpProxy') {
config.pureProxy = true;
} else if (
m === 'keepXFF' ||
m === common.CLIENT_IP_HEADER ||
m === 'forwardedFor'
) {
config.keepXFF = true;
} else if (m === 'x-forwarded-host') {
config.enableFwdHost = true;
} else if (m === 'x-forwarded-proto') {
config.enableFwdProto = true;
} else if (m === 'INADDR_ANY') {
config.INADDR_ANY = true;
} else if (m === 'buildIn' || m === 'build-in') {
process.env.PFORK_EXEC_PATH = process.execPath;
} else if (m === 'safe' || m === 'rejectUnauthorized') {
config.rejectUnauthorized = true;
} else if (m === 'enableRequestHeaderRules') {
config.enableRequestHeaderRules = true;
} else if (m === 'persistentCapture') {
config.isEnableCapture = true;
config.persistentCapture = true;
} else if (['capture', 'intercept', 'enable-capture', 'enableHttps', 'enableHTTPS', 'enableCapture'].indexOf(m) !== -1) {
config.isEnableCapture = true;
} else if (['disable-capture', 'disableCapture'].indexOf(m) !== -1) {
config.isEnableCapture = false;
} else if (['http2', 'enable-http2', 'enableHttp2', 'enable-h2', 'enableH2'].indexOf(m) !== -1) {
config.isEnableHttp2 = true;
} else if (
['disable-http2', 'disableHttp2', 'disable-h2', 'disableH2'].indexOf(m) !== -1
) {
config.isEnableHttp2 = false;
} else if (m === 'hideLeftBar' || m === 'hideLeftMenu') {
config.hideLeftMenu = true;
} else if (DATA_KEY_RE.test(m)) {
var type = RegExp.$1;
var override = RegExp.$2 === '=';
var key = RegExp.$3.toLowerCase();
switch (type) {
case 'clientip':
config.overCipKey = override;
config.cipKey = key;
break;
case 'clientid':
config.overCidKey = override;
config.cidKey = key;
break;
default:
config.overTdKey = override;
config.tdKey = key;
break;
}
} else if (['dnsResolve', 'dnsResolve4', 'dnsResolve6'].indexOf(m) !== -1) {
useResolve = true;
config[m] = true;
} else if (m === 'dnsFallback') {
dnsFallback = true;
}
});
if (useResolve && dnsFallback) {
config.dnsFallback = true;
}
if (config.headless) {
if (!config.pluginsMode) {
config.noGlobalPlugins = true;
}
config.pluginsOnlyMode = true;
config.disableWebUI = true;
delete config.rulesOnlyMode;
} else if (config.shadowRulesMode) {
config.networkMode = true;
delete config.rulesOnlyMode;
delete config.pluginsOnlyMode;
}
if (config.rulesOnlyMode) {
delete config.pluginsOnlyMode;
delete config.networkMode;
delete config.pluginsMode;
config.rulesMode = true;
} else if (config.pluginsOnlyMode) {
delete config.networkMode;
config.rulesMode = true;
config.pluginsMode = true;
} else if (config.networkMode) {
delete config.rulesMode;
delete config.pluginsMode;
} else if (config.rulesMode) {
delete config.pluginsMode;
delete config.networkMode;
}
}
if (/^\d+$/.test(newConf.timeout)) {
config.timeout = +newConf.timeout;
}
setGuestAuth(newConf);
config.disableAllRules = newConf.disableAllRules;
config.disableAllPlugins = newConf.disableAllPlugins;
if (newConf.replaceExistRule === false) {
config.replaceExistRule = false;
} else {
config.replaceExistRule = newConf.replaceRules;
}
if (newConf.replaceExistValue === false) {
config.replaceExistValue = false;
} else {
config.replaceExistValue = newConf.replaceValues;
}
if (common.isString(newConf.certDir)) {
config.certDir = path.resolve(newConf.certDir);
}
var mw = newConf.middlewares || newConf.middleware;
if (typeof mw == 'string') {
config.middlewares = mw.trim().split(/\s*,\s*/);
}
var secureFilter = getSecureFilter(newConf);
if (typeof secureFilter === 'function') {
config.secureFilter = secureFilter;
}
if (typeof newConf.installPlugins === 'function') {
config.installPlugins = newConf.installPlugins;
config.hasInstaller = true;
}
if (typeof newConf.handleWebReq === 'function') {
config.handleWebReq = newConf.handleWebReq;
}
if (typeof newConf.handleUpdate === 'function') {
config.handleUpdate = newConf.handleUpdate;
config.hasUpdater = true;
}
config.notUninstallPluginPaths = getPaths(newConf.notUninstallPluginPath || newConf.notUninstallPluginPaths);
config.pluginPaths = getPaths(newConf.pluginPaths || newConf.pluginsPath || newConf.pluginPath);
config.prePluginsPath = config.projectPluginPaths = getPaths(newConf.projectPluginPaths || newConf.projectPluginsPath || newConf.projectPluginPath);
config.accountPluginsPath = getPaths(newConf.accountPluginPaths || newConf.accountPluginsPath || newConf.accountPluginPath);
config.customPluginPaths = getPaths(newConf.customPluginPaths || newConf.customPluginsPath || newConf.customPluginPath, true);
config.addon = getPaths(newConf.addonsPath || newConf.addonPath || newConf.addon);
if (config.accountPluginsPath) {
config.customPluginPaths = config.accountPluginsPath.concat(config.customPluginPaths || []);
}
if (config.customPluginPaths) {
config.prePluginsPath = (config.prePluginsPath || []).concat(config.customPluginPaths);
}
var pluginHost = newConf.pluginHost;
if (typeof pluginHost === 'string' && (pluginHost = pluginHost.trim())) {
pluginHost = qs.parse(pluginHost);
Object.keys(pluginHost).forEach(function (name) {
var host = pluginHost[name];
if (typeof host === 'string' && (host = host.trim())) {
host = host.toLowerCase().split(/\s*[|,&]\s*/);
host.forEach(function (h) {
config.pluginHostMap[h] = name;
});
}
});
}
var port = getHostPort(newConf.port);
if (port) {
config.host = port.host;
config.uiport = config.port = port.port;
}
if (config.disableWebUI) {
config.notAllowedDisablePlugins = true;
} else {
var uiPort = getHostPort(newConf.uiport);
if (uiPort) {
var curHost = config.host || config.defaultHost;
if (curHost && uiPort.host && curHost !== uiPort.host) {
config.customUIPort = true;
} else {
config.customUIPort = uiPort.port != config.port;
}
config.uiport = uiPort.port;
config.uihost = uiPort.host;
}
}
if (!config.rulesMode) {
config.captureData = true;
}
if (!config.authKey) {
config.authKey = 'auto_' + (Date.now() + Math.random().toFixed(6));
}
config.middlewares = Array.isArray(config.middlewares)
? config.middlewares.map(resolvePath)
: [];
var uiHostList = (config.uiHostList = []);
if (typeof config.localUIHost === 'string') {
var localHostList = config.localUIHost.toLowerCase().split(/\s*[|,&]\s*/);
localHostList.forEach(function (hostname) {
hostname = getHostname(hostname);
if (hostname && LOCAL_UI_HOST_LIST.indexOf(hostname) == -1) {
uiHostList.push(hostname);
}
});
LOCAL_UI_HOST_LIST.push.apply(LOCAL_UI_HOST_LIST, uiHostList);
// 兼容老版本
config.customLocalUIHost = uiHostList[0];
}
config.setLocalUIHost = function (hostname) {
config.customLocalUIHost = getHostname(hostname);
};
config.localUIHost = 'local.whistlejs.com';
var isWebUIHost = function (host) {
if (
host === 'local.wproxy.org' ||
uiHostList.indexOf(host) !== -1 ||
(customUIHost && customUIHost.indexOf(host) !== -1)
) {
return true;
}
if (config.pureProxy) {
return false;
}
return LOCAL_UI_HOST_LIST.indexOf(host) !== -1;
};
var isLocalUIUrl = function (url) {
var host = getHostname(url);
return (
isWebUIHost(host) ||
!!config.pluginHostMap[host] ||
!!customHostPluginMap[host]
);
};
config.isLocalUIUrl = isLocalUIUrl;
exports.isWebUIHost = isWebUIHost;
exports.getPluginNameByHost = function (host) {
host = getHostname(host);
if (isWebUIHost(host)) {
return;
}
return config.pluginHostMap[host] || customHostPluginMap[host];
};
config.sockets = Math.max(parseInt(config.sockets, 10) || 0, 1);
if (config.strict) {
config.sockets = Math.max(config.sockets, 360);
}
var agentConfig = newAgentConf || {
maxSockets: config.sockets,
keepAlive: true,
keepAliveMsecs: KEEP_ALIVE_MSECS,
maxFreeSockets: 0
};
// node 11及以上版本缓存连接有问题,先禁掉
disableAgent = !newAgentConf && !useAgent && (disableAgent || config.debug);
config.httpAgent = disableAgent ? false : createAgent(agentConfig);
config.httpsAgent = disableAgent ? false : createAgent(agentConfig, true);
config.getSocksAgent = function (options) {
var key = getCacheKey(options, true);
var agent = socksAgents.get(key);
if (agent) {
return agent;
}
extend(options, agentConfig);
options.proxyPort = parseInt(options.proxyPort, 10) || 1080;
options.rejectUnauthorized = config.rejectUnauthorized;
options.localDNS = false;
options.auths = getAuths(options);
agent = options.isHttps
? new socks.HttpsAgent(options)
: new socks.HttpAgent(options);
socksAgents.set(key, agent);
agent.on('free', preventThrowOutError);
return agent;
};
var baseDir = config.baseDir
? path.resolve(common.getHomePath(config.baseDir), config.dataDirname)
: (config.whistleName ? path.join(ALL_WHISTLES_PATH, config.whistleName) : getDataDir(config.dataDirname));
var customDirs = path.join(baseDir, 'custom_dirs');
config.baseDir = baseDir;
config.storage = config.storage && encodeURIComponent(config.storage);
if (config.storage) {
baseDir = path.join(customDirs, config.storage);
}
if (config.whistleName) {
config.CUSTOM_PLUGIN_PATH = path.join(baseDir, 'custom_plugins');
config.CUSTOM_CERTS_DIR = path.resolve(baseDir, 'custom_certs');
config.SAVED_SESSIONS_PATH = path.resolve(baseDir, 'saved_sessions');
fse.ensureDirSync(config.CUSTOM_PLUGIN_PATH);
fse.ensureDirSync(config.CUSTOM_CERTS_DIR);
fse.ensureDirSync(config.SAVED_SESSIONS_PATH);
}
fse.ensureDirSync(baseDir);
config.baseDirHash = common.createHash(baseDir);
if (config.password) {
config.passwordHash = common.createHash(config.password);
}
config.rulesDir = path.join(baseDir, 'rules');
config.valuesDir = path.join(baseDir, 'values');
config.propertiesDir = path.join(baseDir, 'properties');
if (config.storage && newConf.copy) {
var copyDir =
typeof newConf.copy == 'string' && encodeURIComponent(newConf.copy);
if (copyDir !== config.storage) {
var dataDir = copyDir ? path.join(customDirs, copyDir) : config.baseDir;
var rulesDir = path.join(dataDir, 'rules');
var valuesDir = path.join(dataDir, 'values');
var propsDir = path.join(dataDir, 'properties');
fse.ensureDirSync(rulesDir);
fse.ensureDirSync(valuesDir);
fse.ensureDirSync(propsDir);
fse.copySync(rulesDir, config.rulesDir);
fse.copySync(valuesDir, config.valuesDir);
fse.copySync(propsDir, config.propertiesDir);
}
}
var clientIdFile = path.join(config.baseDir, '.clientid');
var clientId = readFileText(clientIdFile);
if (!clientId) {
clientId = [
Date.now(),
Math.random(),
Math.random(),
Math.random(),
Math.random(),
Math.random()
].join();
fs.writeFileSync(clientIdFile, clientId);
}
if (config.whistleName) {
clientId += '/' + config.whistleName;
}
config.clientId = clientId;
config.clientIdNo = common.getStatSync(clientIdFile).ino + '';
config.setModified = function (clientId, isRules) {
if (isRules) {
config.mrulesClientId = clientId || '';
config.mrulesTime = Date.now();
} else {
config.mvaluesClientId = clientId || '';
config.mvaluesTime = Date.now();
}
};
return config;
};