whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
2,056 lines (1,812 loc) • 102 kB
JavaScript
var http = require('http');
var path = require('path');
var os = require('os');
var fs = require('fs');
var vm = require('vm');
var net = require('net');
var tls = require('tls');
var crypto = require('crypto');
var fse = require('fs-extra2');
var qs = require('querystring');
var extend = require('extend');
var LRU = require('lru-cache');
var json5 = require('json5');
var iconv = require('iconv-lite');
var zlib = require('zlib');
var dns = require('dns');
var PipeStream = require('pipestream');
var Buffer = require('safe-buffer').Buffer;
var protoMgr = require('../rules/protocols');
var protocols = protoMgr.protocols;
var logger = require('./logger');
var config = require('../config');
var isUtf8 = require('./is-utf8');
var fileMgr = require('./file-mgr');
var httpMgr = require('./http-mgr');
var ReplacePatternTransform = require('./replace-pattern-transform');
var parseQuery = require('./parse-query');
var common = require('./common');
var proc = require('./process');
var parseUrl = require('./parse-url');
var h2Consts = config.enableH2 ? require('http2').constants : {};
var supportsBr = zlib.createBrotliDecompress && zlib.createBrotliCompress;
var toBuffer = fileMgr.toBuffer;
var pendingFiles = {};
var localIpCache = new LRU({ max: 120 });
var CRLF_RE = /\r\n|\r|\n/g;
var LOCALHOST = '127.0.0.1';
var aliasProtocols = protoMgr.aliasProtocols;
var CONTEXT = vm.createContext();
var END_WIDTH_SEP_RE = /[/\\]$/;
var GEN_URL_RE = /^\s*(?:https?:)?\/\/\w[^\s]*\s*$/i;
var CORS_KEY_RE = /^(?:enable|use-credentials|useCredentials|credentials)$/i;
var G_NON_LATIN1_RE = /\s|[^\x00-\xFF]/gu;
var NON_LATIN1_RE = /[^\x00-\xFF]/;
var SCRIPT_START = toBuffer('<script');
var SCRIPT_END = toBuffer('</script>');
var STYLE_START = toBuffer('<style>');
var STYLE_END = toBuffer('</style>');
var PROXY_RE = /^x?(?:socks|https?-proxy|proxy|internal(?:-https)?-proxy)$/;
var SEP_RE = /[|&]/;
var ctxTimer;
var END_RE = /[/\\]$/;
var EXPIRED_SEC = -123456;
var EXP_COOKIE = { maxAge: EXPIRED_SEC, path: '/' };
var EXP_SECURE_COOKIE = { maxAge: EXPIRED_SEC, path: '/', secure: true };
var EXP_COOKIE_D = { maxAge: EXPIRED_SEC, path: '/' };
var EXP_SECURE_COOKIE_D = { maxAge: EXPIRED_SEC, path: '/', secure: true };
var resetContext = function () {
ctxTimer = null;
CONTEXT = vm.createContext();
};
var TIMEOUT_ERR = new Error('Timeout');
var SUB_MATCH_RE = /\$[&\d]/;
var PROTO_NAME_RE = /^([\w.-]+):\/\//;
var replacePattern = ReplacePatternTransform.replacePattern;
var CIPHER_OPTIONS = [
'NULL-SHA256',
'AES128-SHA256',
'AES256-SHA256',
'AES128-GCM-SHA256',
'AES256-GCM-SHA384',
'DH-RSA-AES128-SHA256',
'DH-RSA-AES256-SHA256',
'DH-RSA-AES128-GCM-SHA256',
'DH-RSA-AES256-GCM-SHA384',
'DH-DSS-AES128-SHA256',
'DH-DSS-AES256-SHA256',
'DH-DSS-AES128-GCM-SHA256',
'DH-DSS-AES256-GCM-SHA384',
'DHE-RSA-AES128-SHA256',
'DHE-RSA-AES256-SHA256',
'DHE-RSA-AES128-GCM-SHA256',
'DHE-RSA-AES256-GCM-SHA384',
'DHE-DSS-AES128-SHA256',
'DHE-DSS-AES256-SHA256',
'DHE-DSS-AES128-GCM-SHA256',
'DHE-DSS-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-SHA256',
'ECDHE-RSA-AES256-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-SHA256',
'ECDHE-ECDSA-AES256-SHA384',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ADH-AES128-SHA256',
'ADH-AES256-SHA256',
'ADH-AES128-GCM-SHA256',
'ADH-AES256-GCM-SHA384',
'AES128-CCM',
'AES256-CCM',
'DHE-RSA-AES128-CCM',
'DHE-RSA-AES256-CCM',
'AES128-CCM8',
'AES256-CCM8',
'DHE-RSA-AES128-CCM8',
'DHE-RSA-AES256-CCM8',
'ECDHE-ECDSA-AES128-CCM',
'ECDHE-ECDSA-AES256-CCM',
'ECDHE-ECDSA-AES128-CCM8',
'ECDHE-ECDSA-AES256-CCM8'
];
var TLSV2_CIPHERS = 'ECDHE-ECDSA-AES256-GCM-SHA384';
var cryptoConsts = crypto.constants || {};
var EMPTY_BUFFER = toBuffer('');
var encodeHtml = common.encodeHtml;
var lowerCaseify = common.lowerCaseify;
var removeIPV6Prefix = common.removeIPV6Prefix;
var hasBody = common.hasBody;
var hasProtocol = common.hasProtocol;
var removeProtocol = common.removeProtocol;
var isUrl = common.isUrl;
var joinIpPort = common.joinIpPort;
var workerIndex = process.env && process.env.workerIndex;
var INTERNAL_ID = process.pid + '-' + Math.random();
var pluginMgr;
var RESOLVE_KEY_RE = /^re[qs]Merge:\/\//;
workerIndex = workerIndex >= 0 ? padReqId(config.workerIndex) : '';
exports.TIMEOUT_ERR = TIMEOUT_ERR;
exports.encodeHtml = encodeHtml;
exports.hasProtocol = hasProtocol;
exports.removeProtocol = removeProtocol;
exports.setProtocol = common.setProtocol;
exports.getProtocol = common.getProtocol;
exports.replaceProtocol = common.replaceProtocol;
exports.getPureUrl = common.getPureUrl;
exports.isTunnelHost = common.isTunnelHost;
exports.joinIpPort = joinIpPort;
exports.isWebSocket = common.isWebSocket;
exports.wrapRuleValue = common.wrapRuleValue;
exports.createTransform = common.createTransform;
exports.readFileSync = common.readFileTextSync;
exports.isUrl = isUrl;
exports.workerIndex = workerIndex;
exports.proc = proc;
exports.INTERNAL_ID = INTERNAL_ID;
// 避免属性被 stringify ,减少冗余数据传给前端
exports.PLUGIN_VALUES =
typeof Symbol === 'undefined' ? '_values' : Symbol('_values'); // eslint-disable-line
exports.PLUGIN_MENU_CONFIG =
typeof Symbol === 'undefined' ? '_menuConfig' : Symbol('_menuConfig'); // eslint-disable-line
exports.PLUGIN_INSPECTOR_CONFIG =
typeof Symbol === 'undefined'
? '_inspectorConfig'
: Symbol('_inspectorConfig'); // eslint-disable-line
exports.drain = require('./drain');
exports.isWin = process.platform === 'win32';
exports.isUtf8 = isUtf8;
exports.WhistleTransform = require('./whistle-transform');
exports.ReplacePatternTransform = ReplacePatternTransform;
exports.replacePattern = replacePattern;
exports.ReplaceStringTransform = require('./replace-string-transform');
exports.SpeedTransform = require('./speed-transform');
exports.FileWriterTransform = require('./file-writer-transform');
exports.getServer = require('hagent').getServer;
exports.parseUrl = parseUrl;
exports.request = httpMgr.request;
exports.parseQuery = parseQuery;
exports.localIpCache = localIpCache;
exports.listenerCount = require('./patch').listenerCount;
exports.EMPTY_BUFFER = EMPTY_BUFFER;
function setSecureOptions(options) {
var secureOptions = cryptoConsts.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
if (secureOptions) {
options.secureOptions = secureOptions;
}
return options;
}
exports.setSecureOptions = setSecureOptions;
function noop(_) {
return _;
}
exports.noop = noop;
function isCiphersError(e) {
return (
e.code === 'EPROTO' ||
String(e.message).indexOf(
'disconnected before secure TLS connection was established'
) !== -1
);
}
exports.isCiphersError = isCiphersError;
function getScriptProps(props) {
var result = '';
if (props['use-credentials'] || props.useCredentials){
result += ' crossorigin="use-credentials"';
} else if (props.anonymous) {
result += ' crossorigin="anonymous"';
} else if (props.crossorigin) {
result += ' crossorigin';
}
if (props.defer) {
result += ' defer';
}
if (props.async) {
result += ' async';
}
if (props.nomodule) {
result += ' nomodule';
}
if (props.module) {
result += ' type="module"';
} else if (props.importmap) {
result += ' type="importmap"';
} else if (props.speculationrules) {
result += ' type="speculationrules"';
}
return result;
}
function wrapJs(js, charset, isUrl, props) {
if (!js) {
return '';
}
if (isUrl) {
return toBuffer('<script' + getScriptProps(props) + ' src="' + js + '"></script>', charset);
}
return Buffer.concat([SCRIPT_START, toBuffer(getScriptProps(props) + '>'), toBuffer(js, charset), SCRIPT_END]);
}
function wrapCss(css, charset, isUrl) {
if (!css) {
return '';
}
if (isUrl) {
return toBuffer('<link rel="stylesheet" href="' + css + '" />', charset);
}
return Buffer.concat([STYLE_START, toBuffer(css, charset), STYLE_END]);
}
function evalJson(str) {
try {
return json5.parse(str);
} catch (e) {}
}
exports.parseRawJson = evalJson;
var MAX_LEN = 1024 * 1024 * 5;
function getLatestVersion(registry, cb) {
if (registry && typeof registry !== 'string') {
var name = registry.moduleName;
registry = registry.registry || 'https://registry.npmjs.org';
registry = registry.replace(/\/$/, '') + '/' + name;
}
if (!registry) {
return cb();
}
httpMgr.request(
{
url: registry,
maxLength: MAX_LEN
},
function (err, body, res) {
if (err || res.statusCode !== 200) {
body = null;
} else if (body) {
body = parseJSON(body);
}
body = body && body['dist-tags'];
cb(body && body['latest']);
}
);
}
exports.getRegistry = function(pkg) {
var registry = pkg.whistleConfig && pkg.whistleConfig.registry;
return common.getRegistry(registry);
};
exports.getLatestVersion = getLatestVersion;
exports.isEmptyObject = common.isEmptyObject;
exports.isGroup = common.isGroup;
exports.addTrailerNames = common.addTrailerNames;
exports.removeIllegalTrailers = common.removeIllegalTrailers;
exports.isHead = common.isHead;
exports.hasBody = hasBody;
var ESTABLISHED_CTN =
'HTTP/1.1 200 Connection Established\r\nProxy-Agent: ' +
config.name +
'\r\n\r\n';
exports.setEstablished = function (socket) {
socket.write(ESTABLISHED_CTN);
};
var PORT_RE = /:\d*$/;
exports.changePort = function(url, port) {
var protocol = '';
var index = url.indexOf('://');
if (index !== -1) {
index += 3;
protocol = url.substring(0, index);
url = url.substring(index);
}
index = url.indexOf('/');
if (index != -1) {
var host = url.substring(0, index);
if (net.isIPv6(host)) {
host = '[' + host + ']';
} else {
host = host.replace(PORT_RE, '');
}
url = host + ':' + port + url.substring(index);
}
return protocol + url;
};
function handleStatusCode(statusCode, headers) {
if (statusCode == 401) {
headers['www-authenticate'] = 'Basic realm=User Login';
} else if (statusCode == 407) {
headers['proxy-authenticate'] = 'Basic realm=User Login';
}
return headers;
}
exports.handleStatusCode = handleStatusCode;
function getStatusCode(statusCode) {
statusCode |= 0;
return statusCode < 100 || statusCode > 999 ? 0 : statusCode;
}
exports.getStatusCode = getStatusCode;
function compare(v1, v2) {
return v1 == v2 ? 0 : v1 > v2 ? -1 : 1;
}
exports.compare = compare;
var scriptCache = {};
var VM_OPTIONS = {
displayErrors: false,
timeout: 60
};
var MAX_SCRIPT_SIZE = 1024 * 256;
var MAX_SCRIPT_CACHE_COUNT = 64;
var MIN_SCRIPT_CACHE_COUNT = 32;
function getScript(content) {
content = content.trim();
var len = content.length;
if (!len || len > MAX_SCRIPT_SIZE) {
return;
}
var script = scriptCache[content];
delete scriptCache[content];
var list = Object.keys(scriptCache);
if (list.length > MAX_SCRIPT_CACHE_COUNT) {
list = list
.map(function (content) {
var script = scriptCache[content];
script.content = content;
return script;
})
.sort(function (a, b) {
return compare(a.time, b.time);
})
.splice(0, MIN_SCRIPT_CACHE_COUNT);
scriptCache = {};
list.forEach(function (script) {
scriptCache[script.content] = {
script: script.script,
time: script.time
};
});
}
script = scriptCache[content] = script || {
script: new vm.Script('(function(){\n' + content + '\n})()')
};
script.time = Date.now();
return script.script;
}
function clearContext() {
Object.keys(CONTEXT).forEach(function (key) {
delete CONTEXT[key];
});
if (!ctxTimer) {
ctxTimer = setTimeout(resetContext, 30000);
}
}
exports.execScriptSync = function(script, context) {
try {
if ((script = getScript(script))) {
CONTEXT.console = {};
['fatal', 'error', 'warn', 'info', 'log', 'debug'].forEach(function (
level
) {
CONTEXT.console[level] = logger[level];
});
Object.keys(context).forEach(function (key) {
CONTEXT[key] = context[key];
});
script.runInContext(CONTEXT, VM_OPTIONS);
}
return true;
} catch (e) {
logger.error(e);
} finally {
clearContext();
}
};
function stat(file, callback, force) {
if (force) {
return callback(true);
}
fs.stat(file, function (err) {
if (!err || err.code === 'ENOTDIR') {
return callback();
}
if (err.code === 'ENOENT') {
return callback(true);
}
fs.stat(file, callback);
});
}
function spreadPromise(promise, callback) {
promise.then(function(list) {
callback.apply(null, list);
});
}
function getFileWriter(file, callback, force) {
if (!file) {
return callback();
}
if (END_RE.test(file)) {
file = path.join(file, 'index.html');
}
if (!force && pendingFiles[file]) {
return callback();
}
var execCb = function (writer) {
delete pendingFiles[file];
callback(writer);
};
pendingFiles[file] = 1;
stat(
file,
function (notExists) {
if (!notExists) {
return execCb();
}
fse.ensureFile(file, function (err) {
if (err) {
logger.error(err);
return execCb();
}
execCb(fs.createWriteStream(file).on('error', logger.error));
});
},
force
);
}
function getFileWriters(files, callback, force) {
if (!Array.isArray(files)) {
files = [files];
}
spreadPromise(Promise.all(
files.map(function (file) {
return new Promise(function(resolve) {
getFileWriter(
file,
function (writer) {
resolve(writer);
},
force
);
});
})
), callback);
}
exports.getFileWriters = getFileWriters;
exports.toBuffer = toBuffer;
function getErrorStack(err) {
if (!err) {
return '';
}
var stack;
try {
stack = err.stack;
} catch (e) {}
stack = stack || err.message || err;
var result = [
'From: ' + config.name + '@' + config.version,
'Node: ' + process.version,
'Host: ' + hostname,
'Date: ' + formatDate(),
stack
];
return result.join('\r\n');
}
exports.getErrorStack = getErrorStack;
function formatDate(now) {
now = now || new Date();
return now.toLocaleString();
}
exports.formatDate = formatDate;
var REG_EXP_RE = /^\/(.+)\/(i?u?|ui)$/;
function isRegExp(regExp) {
return REG_EXP_RE.test(regExp);
}
exports.isRegExp = isRegExp;
var ORIG_REG_EXP = /^\/(.+)\/([igmu]{0,4})$/;
function isOriginalRegExp(regExp) {
if (!ORIG_REG_EXP.test(regExp) || /[igmu]{2}/.test(regExp.$2)) {
return false;
}
return true;
}
exports.isOriginalRegExp = isOriginalRegExp;
function toOriginalRegExp(regExp) {
regExp = ORIG_REG_EXP.test(regExp);
try {
regExp = regExp && new RegExp(RegExp.$1, RegExp.$2);
} catch (e) {
regExp = null;
}
return regExp;
}
exports.toOriginalRegExp = toOriginalRegExp;
exports.emitError = function (obj, err) {
if (obj) {
obj.once('error', noop);
obj.emit('error', err || new Error('Unknown'));
}
};
exports.indexOfList = require('./buf-util').indexOf;
exports.startWithList = function (buf, subBuf, start) {
var len = subBuf.length;
if (!len) {
return false;
}
start = start || 0;
for (var i = 0; i < len; i++) {
if (buf[i + start] != subBuf[i]) {
return false;
}
}
return true;
};
exports.endWithList = function (buf, subBuf, end) {
var subLen = subBuf.length;
if (!subLen) {
return false;
}
if (!(end >= 0)) {
end = buf.length - 1;
}
for (var i = 0; i < subLen; i++) {
if (subBuf[subLen - i - 1] != buf[end - i]) {
return false;
}
}
return true;
};
function isEnable(req, name) {
return req.enable[name] && !req.disable[name];
}
exports.isEnable = isEnable;
exports.formatUrl = common.formatUrl;
exports.isKeepClientId = function(req, proxyUrl) {
if (isEnable(req, 'keepClientId')) {
return true;
}
var disable = req.disable;
if (disable.clientId || disable.clientID) {
return false;
}
var enable = req.enable;
return enable.clientId || enable.clientID || proxyUrl;
};
exports.getInternalHost = function (req, host) {
if (isEnable(req, 'useLocalHost')) {
return 'local.wproxy.org';
}
if (host && isEnable(req, 'useSafePort')) {
var index = host.indexOf(':');
if (index !== -1) {
host = host.substring(0, index);
}
host += ':8899';
}
return host;
};
function isAuthCapture(req) {
var e = req.enable || '';
var d = req.disable || '';
return (
(e.authCapture || e.authIntercept) && !d.authCapture && !d.authIntercept
);
}
exports.isAuthCapture = isAuthCapture;
exports.toRegExp = function toRegExp(regExp, ignoreCase) {
regExp = REG_EXP_RE.test(regExp);
try {
regExp = regExp && new RegExp(RegExp.$1, ignoreCase ? 'i' : RegExp.$2);
} catch (e) {
regExp = null;
}
return regExp;
};
const isString = common.isString;
const getFullUrl = common.getFullUrl;
exports.isString = isString;
exports.getFullUrl = getFullUrl;
function disableCSP(headers) {
delete headers['content-security-policy'];
delete headers['content-security-policy-report-only'];
delete headers['x-content-security-policy'];
delete headers['x-content-security-policy-report-only'];
delete headers['x-webkit-csp'];
}
exports.disableCSP = disableCSP;
var interfaces = os.networkInterfaces();
var hostname = os.hostname();
var simpleHostname = '';
var cpus = os.cpus();
var addressList = [];
(function updateSystyemInfo() {
interfaces = os.networkInterfaces();
hostname = os.hostname();
addressList = [];
for (var i in interfaces) {
var list = interfaces[i];
if (Array.isArray(list)) {
list.forEach(function (info) {
addressList.push(info.address.toLowerCase());
});
}
}
setTimeout(updateSystyemInfo, 30000);
})();
if (isString(hostname)) {
simpleHostname = hostname.replace(/[^\w.-]+/g, '').substring(0, 20);
simpleHostname = simpleHostname ? simpleHostname + '.' : '';
}
function createHash(str) {
return crypto.createHash('sha256').update(str).digest('hex');
}
exports.createHash = createHash;
var clientId = [
hostname,
os.platform(),
os.release(),
os.arch(),
cpus.length,
cpus[0] && cpus[0].model,
config.clientId
];
clientId = config.clientId =
simpleHostname +
crypto
.createHmac('sha256', config.CLIENT_ID_HEADER)
.update(clientId.join('\r\n'))
.digest('base64');
config.runtimeId =
simpleHostname +
crypto
.createHmac('sha256', config.CLIENT_ID_HEADER)
.update(clientId + '\r\n' + Math.random() + '\r\n' + Date.now())
.digest('base64') +
'/' +
config.port;
config.runtimeHeaders = { 'x-whistle-runtime-id': config.runtimeId };
config.pluginHeaders = {
'x-whistle-runtime-id': config.runtimeId,
'x-whistle-internal-id': INTERNAL_ID
};
config.pluginHeaders[config.PLUGIN_HOOK_NAME_HEADER] = config.PLUGIN_HOOKS.UI;
exports.setClientId = function (
headers,
enable,
disable,
clientIp,
isInternalProxy
) {
if (disable && (disable.clientId || disable.clientID)) {
return;
}
enable = enable || '';
if (
enable.clientId ||
enable.clientID ||
isInternalProxy
) {
var id = getClientId(headers);
if (
(enable.multiClient || isInternalProxy) &&
!enable.singleClient &&
!disable.multiClient
) {
if (headers[config.CLIENT_ID_HEADER]) {
return;
}
if (!isLocalAddress(clientIp)) {
id += '/' + clientIp;
}
}
headers[config.CLIENT_ID_HEADER] = id;
}
};
function getClientId(headers) {
var id = headers[config.CLIENT_ID_HEADER];
var idKey = config.cidKey;
if (!idKey || (id && !config.overCidKey)) {
return id || clientId;
}
return headers[idKey] || id || clientId;
}
exports.getClientId = getClientId;
exports.getUpdateUrl = common.getUpdateUrl;
exports.getTunnelKey = function (conf) {
var tunnelKey = conf.tunnelKey || conf.tunnelKeys;
if (tunnelKey && typeof tunnelKey === 'string') {
tunnelKey = tunnelKey.toLowerCase().split(/[:,|]/);
tunnelKey = tunnelKey.map(trim).filter(noop);
return tunnelKey.slice(0, 10);
}
};
function getComposerClientId(headers) {
var clientId = headers[config.COMPOSER_CLIENT_ID_HEADER];
if (clientId) {
delete headers[config.COMPOSER_CLIENT_ID_HEADER];
return clientId;
}
}
exports.getComposerClientId = getComposerClientId;
exports.removeClientId = function (headers) {
delete headers[config.CLIENT_ID_HEADER];
};
function networkInterfaces() {
return interfaces;
}
function getHostname() {
return hostname;
}
exports.networkInterfaces = networkInterfaces;
exports.hostname = getHostname;
function getProxyTunnelPath(req, isHttps) {
var host = req._phost && req._proxyTunnel && req.headers.host;
if (isString(host)) {
return host.indexOf(':') !== -1 ? host : host + ':' + (isHttps ? 443 : 80);
}
}
exports.getProxyTunnelPath = getProxyTunnelPath;
function isLocalAddress(address) {
if (isLocalIp(address)) {
return true;
}
address = address.toLowerCase();
if (address[0] === '[') {
address = address.slice(1, -1);
}
if (address == '0:0:0:0:0:0:0:1') {
return true;
}
return localIpCache.get(address) || addressList.indexOf(address) !== -1;
}
exports.isLocalAddress = isLocalAddress;
function isLocalHost(host) {
return host === 'localhost' || isLocalAddress(host);
}
exports.isLocalHost = isLocalHost;
function parseHost(host) {
var index;
if (host[0] === '[') {
index = host.indexOf(']');
return [host.substring(1, index), host.substring(index + 2)];
}
index = host.indexOf(':');
if (index === -1 || host.indexOf(':', index + 1) !== -1) {
return [host, ''];
}
return [host.substring(0, index), host.substring(index + 1)];
}
exports.parseHost = parseHost;
/**
* 解析一些字符时,encodeURIComponent可能会抛异常,对这种字符不做任何处理
* see: http://stackoverflow.com/questions/16868415/encodeuricomponent-throws-an-exception
* @param ch
* @returns
*/
function safeEncodeURIComponent(ch) {
try {
return encodeURIComponent(ch);
} catch (e) {}
return ch;
}
exports.encodeNonLatin1Char = function (str) {
if (!isString(str)) {
return '';
}
return str.replace(G_NON_LATIN1_RE, safeEncodeURIComponent);
};
exports.encodeURIComponent = safeEncodeURIComponent;
function compareUrl(url, fullUrl) {
url = common.getAbsUrl(url, fullUrl);
if (url === fullUrl) {
return true;
}
try {
return url === decodeURIComponent(fullUrl);
} catch (e) {}
}
exports.compareUrl = compareUrl;
function getPath(url, noProtocol) {
if (url) {
url = common.getPureUrl(url);
var index = noProtocol ? -1 : url.indexOf('://');
url = index > -1 ? url.substring(index + 3) : url;
}
return url;
}
exports.getPath = getPath;
function getFilename(url) {
if (typeof url == 'string' && (url = getPath(url).trim())) {
var index = url.lastIndexOf('/');
if (index != -1) {
url = url.substring(index + 1);
} else {
url = null;
}
} else {
url = null;
}
return url || 'index.html';
}
exports.getFilename = getFilename;
function disableReqCache(headers) {
delete headers['if-modified-since'];
delete headers['if-none-match'];
delete headers['last-modified'];
delete headers.etag;
headers['pragma'] = 'no-cache';
headers['cache-control'] = 'no-cache';
}
exports.disableReqCache = disableReqCache;
function disableResStore(headers) {
headers['cache-control'] = 'no-store';
headers['expires'] = new Date(Date.now() - 60000000).toGMTString();
headers['pragma'] = 'no-cache';
delete headers.tag;
}
exports.disableResStore = disableResStore;
function parsePathReplace(urlPath, params) {
if (!params || !/^(?:ws|http)s?:/.test(urlPath)) {
return;
}
var index = urlPath.indexOf('://');
if (index == -1) {
return;
}
index = urlPath.indexOf('/', index + 3) + 1;
if (index <= 0) {
return;
}
var root = urlPath.substring(0, index);
urlPath = urlPath.substring(index);
Object.keys(params).forEach(function (pattern) {
var value = params[pattern];
value = value == null ? '' : value + '';
if (isOriginalRegExp(pattern) && (pattern = toOriginalRegExp(pattern))) {
urlPath = urlPath.replace(pattern, value);
} else if (pattern) {
urlPath = urlPath.split(pattern).join(value);
}
});
root += urlPath;
return root !== urlPath ? root : null;
}
exports.parsePathReplace = parsePathReplace;
function wrapResponse(res) {
var passThrough = common.createTransform();
passThrough.statusCode = res.statusCode;
passThrough.rawHeaderNames = res.rawHeaderNames;
passThrough.headers = lowerCaseify(res.headers);
passThrough.headers['x-server'] = config.name;
res.body != null &&
passThrough.push(Buffer.isBuffer(res.body) ? res.body : String(res.body));
passThrough.push(null);
passThrough.isCustomRes = true;
return passThrough;
}
exports.wrapResponse = wrapResponse;
function wrapGatewayError(body) {
return wrapResponse({
statusCode: 502,
headers: {
'content-type': 'text/html; charset=utf8'
},
body: body
? '<pre>\n' +
encodeHtml(body) +
'\n\n\n<a href="javascript:;" onclick="location.reload()"' +
'>Reload this page</a>\n</pre>'
: ''
});
}
exports.wrapGatewayError = wrapGatewayError;
function sendStatusCodeError(cltRes, svrRes) {
delete svrRes.headers['content-length'];
cltRes.writeHead(502, svrRes.headers);
cltRes.src(wrapGatewayError('Invalid status code: ' + svrRes.statusCode));
}
exports.sendStatusCodeError = sendStatusCodeError;
exports.getQueryValue = function (value) {
if (value && typeof value === 'object') {
try {
return JSON.stringify(value);
} catch (e) {}
}
return value || '';
};
function parseInlineJSON(text, isValue) {
if (!isValue || /\s/.test(text)) {
return;
}
return parseQuery(text, null, null, true);
}
function _parseJSON(data, resolveKeys) {
if (typeof data === 'object') {
return data;
}
if (!isString(data) || !(data = data.trim())) {
return null;
}
return parsePureJSON(data, true) || common.parsePlainText(data, resolveKeys === true);
}
function parseJSON(data) {
return _parseJSON(data);
}
function parsePureJSON(data, isValue) {
return evalJson(data) || parseInlineJSON(data, isValue);
}
exports.parseJSON = parseJSON;
function trim(text) {
return text && text.trim();
}
exports.trim = trim;
exports.lowerCaseify = lowerCaseify;
function parseHeaders(headers, rawNames) {
if (typeof headers == 'string') {
headers = headers.split(CRLF_RE);
}
var _headers = {};
headers.forEach(function (line) {
var index = line.indexOf(':');
var value;
if (index != -1) {
value = line.substring(index + 1).trim();
var rawName = line.substring(0, index).trim();
var name = rawName.toLowerCase();
var list = _headers[name];
if (rawNames) {
rawNames[name] = rawName;
}
if (list) {
if (!Array.isArray(list)) {
_headers[name] = list = [list];
}
list.push(value);
} else {
_headers[name] = value;
}
}
});
return lowerCaseify(_headers);
}
exports.parseHeaders = parseHeaders;
var QUERY_PARAM_RE = /^[^\\/]+=/;
exports.parseRuleJson = function(rules, callback, req) {
if (!Array.isArray(rules)) {
rules = [rules];
}
spreadPromise(Promise.all(
rules.map(function (rule) {
return new Promise(function(resolve) {
readRuleList(rule, resolve, true, null, null, req);
});
})
), callback);
};
function getTempFilePath(filePath, rule) {
var root = rule.root;
if (!root && common.TEMP_PATH_RE.test(filePath)) {
rule._suffix = RegExp.$2;
return path.join(config.TEMP_FILES_PATH, RegExp.$1);
}
filePath = decodePath(filePath);
return root ? join(root, filePath) : filePath;
}
function readRuleValue(rule, callback, checkUrl, needRawData, req) {
if (!rule) {
return callback();
}
if (rule.value) {
return callback(removeProtocol(rule.value, true));
}
var filePath = getMatcherValue(rule);
if (checkUrl && GEN_URL_RE.test(filePath)) {
return callback(filePath);
}
var opts = pluginMgr.resolveKey(filePath, rule, req);
var readFile;
if (opts) {
readFile = pluginMgr[needRawData ? 'requestBin' : 'requestText'];
return readFile(opts, callback);
}
readFile = fileMgr[needRawData ? 'readFile' : 'readFileText'];
filePath = getTempFilePath(filePath, rule);
readFile(filePath, callback);
}
function isGenUrl(value) {
return typeof value === 'string' && GEN_URL_RE.test(value);
}
var CORS_RE = /^re[qs]Cors:\/\//;
function isDeep(result) {
for (var i = 0, len = result.length; i < len; i++) {
if (result[i] === true) {
return true;
}
}
}
function indexOfQuote(matcher) {
return matcher[0] === '"' ? matcher.indexOf('"=') : -1;
}
function getMatcherJson(rule, value) {
var matcher = rule.rawMatcher;
var eq = matcher.indexOf('=');
if (eq === -1) {
return;
}
var and = matcher.indexOf('&');
if (and !== -1 && and > eq) {
return;
}
matcher = removeProtocol(matcher, true);
var len = matcher.length - 1;
var first = matcher[0];
var last = matcher[len];
if (first === '{' && last === '}') {
return;
}
var index;
if (first === '(' && last === ')') {
matcher = matcher.substring(1, len);
} else {
if (!QUERY_PARAM_RE.test(matcher)) {
index = indexOfQuote(matcher);
if (index === -1) {
return;
}
} else if (first === '<' && last === '>') {
matcher = matcher.substring(1, len);
}
}
if (index == null) {
index = indexOfQuote(matcher);
}
var hasQuote;
if (index === -1) {
index = matcher.indexOf('=');
} else {
index += 1;
hasQuote = true;
}
var key = matcher.substring(0, index);
value = value || removeProtocol(getMatcher(rule), true);
if (value.indexOf(key + '=')) {
return;
}
var result = {};
result[hasQuote ? key.substring(1, index - 1) : key] = value.substring(index + 1);
return result;
}
function readRuleList(rule, callback, isJson, charset, isHtml, req) {
if (!rule) {
return callback();
}
var len = rule.list && rule.list.length;
var isBin = protoMgr.isBinProtocol(rule.name);
var needRawData = isBin && !isJson;
if (!len) {
var json = isJson && getMatcherJson(rule);
if (json) {
return callback(json);
}
return readRuleValue(
rule,
isJson
? function (value) {
callback(_parseJSON(value, RESOLVE_KEY_RE.test(rule.matcher)));
}
: callback,
false,
needRawData,
req
);
}
var result = [];
var isJsHtml = isHtml && isBin === 2;
var isCssHtml = isHtml && isBin === 3;
var isRawList = rule.isRawList;
var execCallback = function () {
if (--len > 0) {
return;
}
if (isJson) {
var deepMerge = isDeep(result);
result = result.map(function(text, i) {
var item = rule.list[i];
return _parseJSON(text, item && RESOLVE_KEY_RE.test(item.matcher));
}).filter(noop);
if (result.length > 1) {
result.reverse();
if (typeof result[0] !== 'object') {
result[0] = {};
}
deepMerge && result.unshift(true);
callback(extend.apply(null, result));
} else {
callback(result[0]);
}
} else if (isRawList) {
callback(result);
} else if (isHtml) {
result = result.filter(noop);
callback(result.length ? result : '');
} else {
callback(fileMgr.joinData(result, !isBin, charset));
}
};
var isCors = CORS_RE.test(rule.matcher);
var checkUrl = isJsHtml || isCssHtml;
rule.list.forEach(function (r, i) {
if (isJson) {
var value = removeProtocol(getMatcher(r), true);
var json = getMatcherJson(r, value);
if (json) {
result[i] = json;
return execCallback();
}
value = value && value.trim();
if (value) {
if (isCors) {
if (GEN_URL_RE.test(value)) {
json = { origin: value };
} else if (value === '*') {
json = { '*': '' };
} else if (CORS_KEY_RE.test(value)) {
json = { enable: true };
}
}
json = json || parsePureJSON(value, QUERY_PARAM_RE.test(value));
if (json) {
result[i] = json;
return execCallback();
}
}
}
readRuleValue(
r,
function (value) {
if (isHtml && value) {
var props = r.lineProps;
if (checkUrl) {
var isUrl = isGenUrl(value);
var wrap = isJsHtml ? wrapJs : wrapCss;
value = wrap(isUrl ? value.trim() : value, charset, isUrl, props);
} else {
value = toBuffer(value, charset);
}
var strictHtml = props.strictHtml;
var safeHtml = props.safeHtml;
if (strictHtml || safeHtml) {
value._strictHtml = strictHtml;
value._safeHtml = safeHtml;
r.lineStrict = strictHtml;
r.lineSafe = safeHtml;
}
}
result[i] = value;
execCallback();
},
checkUrl,
needRawData,
req
);
});
}
exports.getRuleValue = function(rules, callback, noBody, charset, isHtml, req) {
if (noBody || !rules) {
return callback();
}
if (!Array.isArray(rules)) {
rules = [rules];
}
spreadPromise(Promise.all(
rules.map(function (rule) {
return new Promise(function(resolve) {
readRuleList(rule, resolve, false, charset, isHtml, req);
});
})
), callback);
};
function decodePath(path) {
path = getPath(path, true);
try {
return decodeURIComponent(path);
} catch (e) {
logger.error(e);
}
try {
return qs.unescape(path);
} catch (e) {
logger.error(e);
}
return path;
}
exports.getRuleFiles = function(rule, req) {
var files = rule.files || [getPath(getUrl(rule))];
var rawFiles = rule.rawFiles || files;
var result = [];
files.map(function (file, i) {
var opts = pluginMgr.resolveKey(rawFiles[i], rule, req);
if (opts) {
result.push(opts);
} else {
file = getTempFilePath(file, rule);
file = fileMgr.convertSlash(file);
if (END_WIDTH_SEP_RE.test(file)) {
result.push(file.slice(0, -1));
result.push(join(file, 'index.html'));
} else {
result.push(file);
}
}
});
return result;
};
exports.getRuleFile = function(rule) {
var filePath = getPath(getUrl(rule));
if (!filePath) {
return filePath;
}
return rule.root
? join(rule.root, decodePath(filePath))
: decodePath(filePath);
};
function getValue(rule) {
return rule.value || rule.path;
}
function trimUrl(url) {
if (!url) {
return url;
}
var index = url.indexOf('://');
if (index === -1) {
return url.trim();
}
index += 3;
var protocol = url.substring(0, index);
return protocol + url.substring(index).trim();
}
function getMatcher(rule) {
return trimUrl(rule && (getValue(rule) || rule.matcher));
}
function getUrl(rule) {
return trimUrl(rule && (getValue(rule) || rule.url));
}
exports.rule = {
getMatcher: getMatcher,
getUrl: getUrl
};
function getMatcherValue(rule) {
rule = getMatcher(rule);
return rule && removeProtocol(rule, true);
}
exports.getMatcherValue = getMatcherValue;
exports.getUrlValue = function(rule) {
rule = getUrl(rule);
return rule && removeProtocol(rule, true);
};
function _getRawType(type) {
return typeof type === 'string' ? type.split(';')[0].toLowerCase() : '';
}
function getRawType(data) {
return _getRawType(data.headers && data.headers['content-type']);
}
exports.getRawType = getRawType;
function getContentType(contentType) {
if (contentType && typeof contentType != 'string') {
contentType = contentType['content-type'] || contentType.contentType;
}
contentType = _getRawType(contentType);
if (!contentType) {
return;
}
if (contentType.indexOf('javascript') != -1) {
return 'JS';
}
if (contentType.indexOf('css') != -1) {
return 'CSS';
}
if (contentType.indexOf('html') != -1) {
return 'HTML';
}
if (contentType.indexOf('json') != -1) {
return 'JSON';
}
if (contentType.indexOf('xml') != -1) {
return 'XML';
}
if (contentType.indexOf('text/') != -1) {
return 'TEXT';
}
if (contentType.indexOf('image/') != -1) {
return 'IMG';
}
}
exports.getContentType = getContentType;
function isText(contentType) {
contentType = contentType && getContentType(contentType);
return contentType && contentType !== 'IMG';
}
exports.isText = isText;
function supportHtmlTransform(res, req) {
var headers = res.headers;
if (getContentType(headers) != 'HTML' || !hasBody(res, req)) {
return false;
}
var contentEncoding = getContentEncoding(headers);
//chrome新增了sdch压缩算法,对此类响应无法解码,deflate无法区分deflate还是deflateRaw
return !contentEncoding || contentEncoding == 'gzip' || contentEncoding === 'br';
}
exports.supportHtmlTransform = supportHtmlTransform;
exports.getEnableEncoding = function(enable) {
if (!enable) {
return;
}
if (enable.br) {
return supportsBr ? 'br' : null;
}
if (enable.gzip) {
return 'gzip';
}
if (enable.deflate) {
return 'deflate';
}
};
function removeUnsupportsHeaders(headers, supportsDeflate) {
//只保留支持的zip格式:gzip、deflate
if (!headers || !headers['accept-encoding']) {
return;
}
if (config.noGzip) {
delete headers['accept-encoding'];
return;
}
var list = headers['accept-encoding'].trim().split(/\s*,\s*/g);
var acceptEncoding = [];
for (var i = 0, len = list.length; i < len; i++) {
var ae = list[i].toLowerCase();
if (ae && ((supportsDeflate && ae == 'deflate') || ae == 'gzip' || (supportsBr && ae === 'br'))) {
acceptEncoding.push(ae);
}
}
if ((acceptEncoding = acceptEncoding.join(', '))) {
headers['accept-encoding'] = acceptEncoding;
}
}
exports.removeUnsupportsHeaders = removeUnsupportsHeaders;
function hasRequestBody(req) {
req = typeof req == 'string' ? req : req.method;
if (typeof req != 'string') {
return false;
}
req = req.toUpperCase();
return !(
req === 'GET' ||
req === 'HEAD' ||
req === 'OPTIONS' ||
req === 'CONNECT'
);
}
exports.hasRequestBody = hasRequestBody;
function getContentEncoding(headers) {
var encoding = toLowerCase(
(headers && headers['content-encoding']) || headers
);
return encoding === 'gzip' || (supportsBr && encoding === 'br') ||
encoding === 'deflate' ? encoding : null;
}
exports.getContentEncoding = getContentEncoding;
function getZipStream(headers) {
switch (getContentEncoding(headers)) {
case 'gzip':
return zlib.createGzip();
case 'br':
return supportsBr && zlib.createBrotliCompress();
case 'deflate':
return zlib.createDeflate();
}
}
exports.isZip = function(encoding, chunk) {
if (encoding === 'gzip') {
return chunk[0] === 31 && (chunk[1] == null || chunk[1] === 139);
}
if (encoding === 'br') {
return supportsBr;
}
return encoding === 'deflate';
};
function getUnzipStream(headers) {
switch (getContentEncoding(headers)) {
case 'gzip':
return zlib.createGunzip();
case 'br':
return supportsBr && zlib.createBrotliDecompress();
case 'deflate':
return zlib.createInflate();
}
}
exports.getZipStream = getZipStream;
exports.getUnzipStream = getUnzipStream;
exports.isWhistleTransformData = function (obj) {
if (!obj) {
return false;
}
return obj.speed > 0 || obj.delay > 0 || obj.top || obj.body || obj.bottom;
};
function getPipeIconvStream(headers) {
var pipeStream = new PipeStream();
var charset = getCharset(headers['content-type']);
if (charset) {
pipeStream.addHead(iconv.decodeStream(charset));
pipeStream.addTail(iconv.encodeStream(charset));
} else {
pipeStream.addHead(function (res, next) {
var buffer, iconvDecoder;
res.on('data', function (chunk) {
buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk;
resolveCharset(buffer);
});
res.on('end', resolveCharset);
function resolveCharset(chunk) {
if (!charset) {
if (chunk && buffer.length < 25600) {
return;
}
charset = !buffer || isUtf8(buffer) ? 'utf8' : 'GB18030';
}
if (!iconvDecoder) {
iconvDecoder = iconv.decodeStream(charset);
next(iconvDecoder);
}
if (buffer) {
iconvDecoder.write(buffer);
buffer = null;
}
!chunk && iconvDecoder.end();
}
});
pipeStream.addTail(function (src, next) {
next(src.pipe(iconv.encodeStream(charset)));
});
}
return pipeStream;
}
exports.getPipeIconvStream = getPipeIconvStream;
function toLowerCase(str) {
return typeof str == 'string' ? str.trim().toLowerCase() : str;
}
exports.toLowerCase = toLowerCase;
function toUpperCase(str) {
return typeof str == 'string' ? str.trim().toUpperCase() : str;
}
exports.toUpperCase = toUpperCase;
var CHARSET_RE = /charset=([\w-]+)/i;
function getCharset(str) {
var charset;
if (CHARSET_RE.test(str)) {
charset = RegExp.$1;
if (!iconv.encodingExists(charset)) {
return;
}
}
return charset;
}
exports.getCharset = getCharset;
function getClientIpFH(headers, name) {
var val = headers[name];
if (!isString(val)) {
return '';
}
var index = val.indexOf(',');
if (index !== -1) {
val = val.substring(0, index);
}
val = removeIPV6Prefix(val.trim());
return net.isIP(val) && !isLocalAddress(val) ? val : '';
}
function getForwardedFor(headers) {
var ip = getClientIpFH(headers, config.CLIENT_IP_HEAD);
var cipKey = config.cipKey;
if (cipKey && (!ip || config.overCipKey)) {
ip = getClientIpFH(headers, cipKey) || ip;
}
return ip;
}
exports.getForwardedFor = getForwardedFor;
function isLocalIp(ip) {
if (!isString(ip)) {
return true;
}
return ip.length < 7 || ip === LOCALHOST;
}
function getRemoteAddr(req) {
try {
var socket = req.socket || req;
if (!socket._remoteAddr) {
var ip = req.headers && req.headers[config.REMOTE_ADDR_HEAD];
if (ip) {
socket._remoteAddr = ip;
delete req.headers[config.REMOTE_ADDR_HEAD];
} else {
socket._remoteAddr =
removeIPV6Prefix(socket.remoteAddress) || LOCALHOST;
}
}
return socket._remoteAddr;
} catch (e) {}
return LOCALHOST;
}
exports.getRemoteAddr = getRemoteAddr;
function getClientIp(req, ip) {
ip = ip || getForwardedFor(req.headers || {}) || getRemoteAddr(req);
return isLocalIp(ip) ? LOCALHOST : ip;
}
exports.getClientIp = getClientIp;
function getRemotePort(req) {
try {
var socket = req.socket || req;
if (socket._remotePort == null) {
var port = req.headers && req.headers[config.REMOTE_PORT_HEAD];
if (port) {
delete req.headers[config.REMOTE_PORT_HEAD];
} else {
port = socket.remotePort;
}
socket._remotePort = port > 0 ? port : '0';
}
return socket._remotePort;
} catch (e) {}
return 0;
}
exports.getRemotePort = getRemotePort;
exports.getClientPort = function (req) {
var headers = req.headers || {};
var port = headers[config.CLIENT_PORT_HEAD];
if (port > 0) {
return port;
}
return getRemotePort(req);
};
exports.removeIPV6Prefix = removeIPV6Prefix;
exports.isUrlEncoded = common.isUrlEncoded;
function isJSONContent(req) {
if (!hasRequestBody(req)) {
return false;
}
return getContentType(req.headers) === 'JSON';
}
exports.isJSONContent = isJSONContent;
function isProxyPort(proxyPort) {
return (
proxyPort == config.port ||
proxyPort == config.httpsPort ||
proxyPort == config.httpPort ||
proxyPort == config.socksPort ||
proxyPort == config.realPort
);
}
exports.isProxyPort = isProxyPort;
exports.isLocalPHost = function(req, isHttps) {
var phost = req._phost;
var hostname = phost && phost.hostname;
if (!hostname || !isProxyPort(phost.port || (isHttps ? 443 : 80))) {
return false;
}
return isLocalHost(hostname);
};
function isMultipart(req) {
return /multipart/i.test(req.headers['content-type']);
}
exports.isMultipart = isMultipart;
function getQueryString(url) {
var index = url.indexOf('?');
return index == -1 ? '' : url.substring(index + 1);
}
exports.getQueryString = getQueryString;
function replaceQueryString(query, replaceQuery, delProps) {
if (replaceQuery && typeof replaceQuery != 'string') {
replaceQuery = qs.stringify(replaceQuery);
}
if (delProps ? (!query && !replaceQuery) : (!query || !replaceQuery)) {
return replaceQuery || query;
}
var queryList = [];
var params = {};
var curParams = {};
var name, value;
var parseKey = (item) => {
var index = item.indexOf('=');
if (index == -1) {
name = item;
value = '';
} else {
name = item.substring(0, index);
value = item.substring(index + 1);
}
};
var addValue = function(obj) {
var curVal = obj[name];
if (Array.isArray(curVal)) {
curVal.push(value);
} else if (curVal != null) {
curVal = [curVal, value];
}
obj[name] = curVal || value;
};
if (replaceQuery) {
replaceQuery = replaceQuery.split('&').map(function(item) {
parseKey(item);
if (!delProps || !delProps[name]) {
addValue(params);
}
});
}
if (query) {
query.split('&').map(function(item) {
parseKey(item);
if ((!delProps || !delProps[name]) && params[name] == null) {
addValue(curParams);
}
});
}
extend(curParams, params);
Object.keys(curParams).forEach(function(key) {
var val = curParams[key];
if (Array.isArray(val)) {
val.forEach(function(v) {
queryList.push(key + '=' + v);
});
} else {
queryList.push(key + '=' + val);
}
});
return queryList.join('&');
}
exports.replaceQueryString = replaceQueryString;
exports.replaceUrlQueryString = function(url, queryString) {
if (!queryString) {
return url;
}
url = url || '';
var hashIndex = url.indexOf('#');
var hashString = '';
if (hashIndex != -1) {
hashString = url.substring(hashIndex);
url = url.substring(0, hashIndex);
}
queryString = replaceQueryString(getQueryString(url), queryString);
return (
url.replace(/\?.*$/, '') +
(queryString ? '?' + queryString : '') +
hashString
);
};
exports.decodeBuffer = fileMgr.decode;
function setHeaders(data, obj) {
if (!data.headers) {
data.headers = {};
}
for (var i in obj) {
data.headers[i] = obj[i];
}
return data;
}
exports.setHeaders = setHeaders;
function setHeader(data, name, value) {
if (!data.headers) {
data.headers = {};
}
data.headers[name] = value;
return data;
}
exports.setHeader = setHeader;
function join(root, dir) {
return root ? path.resolve(root, dir) : dir;
}
exports.join = join;
function resolveProperties(list, result) {
result = result || {};
if (list) {
list
.map(getMatcherValue)
.join('|')
.split(SEP_RE)
.forEach(function (action) {
if (action) {
result[action] = true;
}
});
}
return result;
}
exports.resolveProperties = resolveProperties;
exports.parseLineProps = function (str) {
str = str && removeProtocol(str, true);
if (!str) {
return;
}
var result = {};
str.split(SEP_RE).forEach(function (action) {
if (action) {
result[action] = true;
}
});
return result;
};
function resolveIgnore(ignore) {
var keys = Object.keys(ignore);
var exclude = {};
var ignoreAll, disableIgnoreAll;
ignore = {};
keys.forEach(function (name) {
if (name.indexOf('ignore.') === 0 || name.indexOf('ignore:') === 0) {
exclude[name.substring(7)] = 1;
return;
}
if (name.indexOf('-') === 0 || name.indexOf('!') === 0) {
name = name.substring(1);
if (name === '*') {
disableIgnoreAll = true;
} else {
exclude[name] = 1;
}
return;
}
name = name.replace('ignore|', '');
if (name === 'filter' || name === 'ignore') {
return;
}
if (
name === 'allRules' ||
name === 'allProtocols' ||
name === 'All' ||
name === '*'
) {
ignoreAll = true;
return;
}
ignore[aliasProtocols[name] || name] = 1;
});
if (ignoreAll && !disableIgnoreAll) {
protocols.forEach(function (name) {
ignore[name] = 1;
});
keys = protocols;
} else {
keys = Object.keys(ignore);
}
keys.forEach(function (name) {
if (exclude[name]) {
delete ignore[name];
}
});
return {
ignoreAll: ignoreAll,
exclude: exclude,
ignore: ignore
};
}
function resolveFilter(ignore, filter) {
filter = filter || {};
var result = resolveIgnore(ignore);
ignore = result.ignore;
Object.keys(ignore).forEach(function (name) {
if (protocols.indexOf(name) === -1) {
filter['ignore|' + name] = true;
} else {
filter[name] = true;
}
});
Object.keys(result.exclude).forEach(function (name) {
filter['ignore:' + name] = 1;
});
if (result.ignoreAll) {
filter.allRules = 1;
}
return filter;
}
exports.resolveFilter = resolveFilter;
exports.isIgnored = function (filter, name) {
return (
!filter['ignore:' + name] && (filter[name] || filter['ignore|' + name])
);
};
function exactIgnore(filter, rule) {
if (filter['ignore|' + 'pattern=' + rule.rawPattern]) {
return true;
}
if (filter['ignore|' + 'matcher=' + rule.matcher]) {
return true;
}
return filter['ignore|' + 'matcher=' + rule.r