whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
1,976 lines (1,742 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 crypto = require('crypto');
var fse = require('fs-extra2');
var qs = require('querystring');
var extend = require('extend');
var LRU = require('lru-cache');
var iconv = require('iconv-lite');
var zlib = require('zlib');
var dns = require('dns');
var mime = require('mime');
var PipeStream = require('pipestream');
var protoMgr = require('../rules/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 common = require('./common');
var proc = require('./process');
var parseUrl = require('./parse-url');
var h2Consts = config.enableH2 ? require('http2').constants : {};
var protocols = protoMgr.protocols;
var resProtocols = protoMgr.resProtocols;
var uid = config.uid;
var parseQuery = common.parseQuery;
var supportsBr = common.supportsBr;
var isLocalIp = common.isLocalIp;
var getStat = common.getStat;
var toBuffer = common.toBuffer;
var pendingFiles = {};
var localIpCache = new LRU({ max: 120 });
var LOCALHOST = '127.0.0.1';
var aliasProtocols = protoMgr.aliasProtocols;
var CONTEXT = vm.createContext();
var END_SLASH_RE = /[/\\]$/;
var GEN_URL_RE = /^\s*(?:https?:)?\/\/\w[^\s]*\s*$/i;
var CORS_KEY_RE = /^(?:enable|use-credentials|useCredentials|credentials)$/i;
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 = common.TIMEOUT_ERR;
var SUB_MATCH_RE = /\$[&\d]/;
var PROTO_NAME_RE = /^([\w.-]+):\/\//;
var replacePattern = ReplacePatternTransform.replacePattern;
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 getTlsOptions = common.getTlsOptions;
var isUrl = common.isUrl;
var joinIpPort = common.joinIpPort;
var getContentEncoding = common.getContentEncoding;
var getUnzipStream = common.getUnzipStream;
var toLowerCase = common.toLowerCase;
var safeEncodeURIComponent = common.safeEncodeURIComponent;
var encodeNonLatin1Char = common.encodeNonLatin1Char;
var toUpperCase = common.toUpperCase;
var getCharset = common.getCharset;
var hasRequestBody = common.hasRequestBody;
var parseRawJson = common.parseRawJson;
var getMatcher = common.getMatcher;
var getMatcherValue = common.getMatcherValue;
var getRemoteAddr = common.getRemoteAddr;
var getRemotePort = common.getRemotePort;
var workerIndex = process.env && process.env.workerIndex;
var INTERNAL_ID = Date.now().toString(16) + Math.floor(Math.random() * 10000000).toString(16);
var pluginMgr;
var RESOLVE_KEY_RE = /^re[qs]Merge:\/\//;
var CONTROL_RE =
/[\u001e\u001f\u200e\u200f\u200d\u200c\u202a\u202d\u202e\u202c\u206e\u206f\u206b\u206a\u206d\u206c]+/g;
var MULTI_LINE_VALUE_RE =
/^[^\n\r\S]*(```+)[^\n\r\S]*(\S+)[^\n\r\S]*[\r\n](?:([\s\S]*?)[\r\n])??[^\n\r\S]*\1\s*$/gm;
var SPACE_RE = /\s/;
var PATH_RE = /^[\w./-]+$/;
var SLASH_RE = /[\\/]/;
var ROOT_STYLE = ':root{background-color:#121212;color:#f0f0f0;}';
var THEME_STYLE = '<style>@media (prefers-color-scheme: dark) {:not([data-theme="light"])'
+ ROOT_STYLE + '}\n' + '[data-theme="dark"]' + ROOT_STYLE + '</style>\n';
var SNI_PLUGIN_HEADER = 'x-whistle-sni-plugin-' + uid;
var INTERNAL_ID_HEADER = 'x-whistle-internal-id';
var TEMP_TUNNEL_DATA_HEADER = 'x-whistle-tunnel-data-' + uid;
var TUNNEL_DATA_HEADER = 'x-whistle-tunnel-data';
var FWD_HOST_HEADER = 'x-forwarded-host';
var FWD_PROPS_HEADER = 'x-whistle-forwarded-props';
var REAL_HOST_HEADER = common.REAL_HOST_HEADER;
var HTTPS_PROTO_HEADER = 'x-forwarded-proto';
var clientIpKey = common.CLIENT_IP_HEADER;
var clientInfoKey = config.CLIENT_INFO_HEADER;
exports.SNI_PLUGIN_HEADER = SNI_PLUGIN_HEADER;
exports.INTERNAL_ID_HEADER = INTERNAL_ID_HEADER;
exports.TEMP_TUNNEL_DATA_HEADER = TEMP_TUNNEL_DATA_HEADER;
exports.TUNNEL_DATA_HEADER = TUNNEL_DATA_HEADER;
exports.REAL_HOST_HEADER = REAL_HOST_HEADER;
exports.HTTPS_PROTO_HEADER = HTTPS_PROTO_HEADER;
exports.ADDITIONAL_HEAD = 'x-whistle-additional-headers';
workerIndex = workerIndex >= 0 ? common.padLeft(config.workerIndex, 3) : '';
exports.sendRes = common.sendRes;
exports.THEME_STYLE = THEME_STYLE;
exports.encodeNonLatin1Char = encodeNonLatin1Char;
exports.isJson = common.isJson;
exports.encodeURIComponent = safeEncodeURIComponent;
exports.getStat = getStat;
exports.toLowerCase = toLowerCase;
exports.toUpperCase = toUpperCase;
exports.getCharset = getCharset;
exports.getContentEncoding = getContentEncoding;
exports.getUnzipStream = getUnzipStream;
exports.hasRequestBody = hasRequestBody;
exports.TIMEOUT_ERR = TIMEOUT_ERR;
exports.getTlsOptions = getTlsOptions;
exports.encodeHtml = encodeHtml;
exports.hasProtocol = hasProtocol;
exports.removeProtocol = removeProtocol;
exports.parseRawJson = parseRawJson;
exports.getMatcherValue = getMatcherValue;
exports.setProtocol = common.setProtocol;
exports.getProtocol = common.getProtocol;
exports.replaceProtocol = common.replaceProtocol;
exports.getMethod = common.getMethod;
exports.sendGzip = common.sendGzip;
exports.sendGzipText = common.sendGzipText;
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.parseHeaders = common.parseHeaders;
exports.connect = common.connect;
exports.isUrl = isUrl;
exports.workerIndex = workerIndex;
exports.proc = proc;
exports.INTERNAL_ID = INTERNAL_ID;
// 避免属性被 stringify ,减少冗余数据传给前端
exports.PLUGIN_VALUES = Symbol('values');
exports.PLUGIN_MENU_CONFIG = Symbol('menuConfig');
exports.PLUGIN_INSPECTOR_CONFIG = Symbol('inspectorConfig');
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.parseQuery = parseQuery;
exports.localIpCache = localIpCache;
exports.getRemoteAddr = getRemoteAddr;
exports.getRemotePort = getRemotePort;
exports.listenerCount = require('./patch').listenerCount;
exports.EMPTY_BUFFER = EMPTY_BUFFER;
var NOT_SUPPORTED_ERR = new Error('Unsupported');
NOT_SUPPORTED_ERR.code = 502;
function request(options, callback) {
if (options && options.isInternalReq) {
return callback && callback(NOT_SUPPORTED_ERR, '', '');
}
return httpMgr.request(common.setInternalOptions(options, config, true), callback);
}
exports.request = request;
function getInlineKey(key, file) {
return file ? key + '\n\r' + file : key;
}
exports.getInlineKey = getInlineKey;
function resolveInlineValues(str, inlineValues, file) {
str = str && str.replace(CONTROL_RE, '').trim();
if (!str || str.indexOf('```') === -1) {
return str;
}
return str.replace(MULTI_LINE_VALUE_RE, function (_, __, key, value) {
key = getInlineKey(key, file);
if (inlineValues && inlineValues[key] == null) {
inlineValues[key] = value || '';
}
return '';
});
}
exports.resolveInlineValues = resolveInlineValues;
exports.getPluginFile = function(name) {
return 'Plugin: ' + name;
};
exports.toPrivateValues = function(vals, file) {
if (!vals) {
return vals;
}
var keys = Object.keys(vals);
if (!keys.length) {
return vals;
}
var result = {};
keys.forEach(function(key) {
result[getInlineKey(key, file)] = vals[key];
});
return result;
};
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) {
var c = e.code;
return (
c === 'EPROTO' || c === 'ERR_SSL_BAD_ECPOINT' || c === 'ERR_SSL_VERSION_OR_CIPHER_MISMATCH' ||
String(e.message).indexOf('disconnected before secure TLS connection was established') !== -1
);
}
exports.isCiphersError = isCiphersError;
exports.setFramesMode = function(headers, enable) {
if (enable) {
headers['x-whistle-frames-mode'] = '1';
} else {
delete headers['x-whistle-frames-mode'];
}
};
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]);
}
var MAX_LEN = 1024 * 1024 * 5;
exports.getLatestVersion = function(registry, cb, field) {
if (registry && typeof registry !== 'string') {
var name = registry.moduleName;
registry = registry.registry ||
(registry.installRegistry && registry.installRegistry[0]) ||
'https://registry.npmjs.org';
registry = registry.replace(/\/$/, '') + '/' + name;
}
if (!registry) {
return cb();
}
request(
{
url: registry,
maxLength: MAX_LEN,
responseType: 'json'
},
function (_, body) {
if (isString(field)) {
body = body && body[field];
} else {
body = body && body['dist-tags'];
body = body && body['latest'];
}
cb(body);
}
);
};
exports.getRegistry = function(pkg) {
var registry = pkg.whistleConfig && pkg.whistleConfig.registry;
return common.getRegistry(registry);
};
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.appName +
'\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 checkWriterFile(file, callback, force) {
if (force) {
return callback(true);
}
getStat(file, function (err) {
if (!err || err.code === 'ENOTDIR') {
return callback();
}
if (err.code === 'ENOENT') {
return callback(true);
}
callback(err);
});
}
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;
checkWriterFile(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 handleCallback(list, fn, callback) {
if (!Array.isArray(list)) {
list = [list];
}
var len = list.length;
if (!len) {
return callback();
}
var result = [];
list.forEach(function(item, i) {
fn(item, function(value) {
result[i] = value;
if (--len === 0) {
callback.apply(null, result);
}
});
});
}
exports.getFileWriters = function (files, callback, force) {
handleCallback(files, function(file, cb) {
getFileWriter(file, cb, force);
}, callback);
};
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.appName + '@' + 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})$/;
var FLAGS_RE = /[igmu]{2}/;
function isOriginalRegExp(regExp) {
if (!ORIG_REG_EXP.test(regExp) || FLAGS_RE.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.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.isDisable = function(req, name) {
return req.disable[name] && !req.enable[name];
};
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;
};
var isString = common.isString;
var 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 = [];
var usiTimer;
function updateSystyemInfo() {
clearTimeout(usiTimer);
interfaces = os.networkInterfaces();
hostname = os.hostname();
addressList = [];
common.walkInterfaces(function (info) {
addressList.push(info.address.toLowerCase());
});
usiTimer = setTimeout(updateSystyemInfo, 36000);
}
updateSystyemInfo();
process.on('w2NetworkInterfacesChange', updateSystyemInfo);
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 };
config.pluginHeaders[INTERNAL_ID_HEADER] = 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 (isString(tunnelKey)) {
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;
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 notNull(value) {
return value != null;
}
var HTTP_PROTO_RE = /^(?:ws|http)s?:/;
function parsePathReplace(urlPath, params, delPaths) {
if ((!params && !delPaths) || !HTTP_PROTO_RE.test(urlPath)) {
return;
}
var index = urlPath.indexOf('://');
if (index == -1) {
return;
}
index = urlPath.indexOf('/', index + 3) + 1;
if (!index) {
return;
}
var curPath = urlPath.substring(index);
params && Object.keys(params).forEach(function (pattern) {
var value = params[pattern];
value = value == null ? '' : value + '';
if (isOriginalRegExp(pattern) && (pattern = toOriginalRegExp(pattern))) {
curPath = curPath.replace(pattern, value);
} else if (pattern) {
curPath = curPath.split(pattern).join(value);
}
});
if (curPath && delPaths) {
var query = '';
var qIdx = curPath.indexOf('?');
if (qIdx !== -1) {
query = curPath.substring(qIdx);
curPath = curPath.substring(0, qIdx);
}
if (curPath) {
if (delPaths.all) {
delete delPaths.all;
curPath = query;
} else {
curPath = curPath.split('/');
var len = curPath.length;
var last = delPaths.last;
if (last) {
delete delPaths.last;
delPaths[len - 1] = 1;
}
Object.keys(delPaths).forEach(function (key) {
key = +key;
key = key < 0 ? len + key : key;
if (key >= 0 && key < len) {
curPath[key] = null;
}
});
curPath = curPath.filter(notNull);
if (last && curPath[curPath.length - 1]) {
curPath.push('');
}
curPath = curPath.join('/');
}
}
curPath += query;
}
var newUrl = urlPath.substring(0, index) + curPath;
return newUrl === urlPath ? null : newUrl;
}
exports.parsePathReplace = parsePathReplace;
function wrapResponse(res, realUrl) {
var newRes = common.createTransform();
newRes.statusCode = res.statusCode;
newRes.rawHeaderNames = res.rawHeaderNames;
newRes.headers = lowerCaseify(res.headers);
newRes.headers['x-server'] = config.appName;
res.body != null &&
newRes.push(Buffer.isBuffer(res.body) ? res.body : String(res.body));
newRes.push(null);
newRes.isCustomRes = true;
newRes.realUrl = realUrl;
return newRes;
}
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 || SPACE_RE.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 parseRawJson(data) || parseInlineJSON(data, isValue);
}
exports.parseJSON = parseJSON;
function trim(text) {
return text && text.trim();
}
exports.trim = trim;
exports.lowerCaseify = lowerCaseify;
var QUERY_PARAM_RE = /^[^\\/]+=/;
function tryParseMatcher(text, rule) {
var matcher = !text && removeProtocol(getMatcher(rule), true);
if (!matcher || matcher.indexOf('=') === -1) {
return;
}
return parseQuery(matcher, null, null, true);
}
exports.parseRuleJson = function(rules, callback, req) {
handleCallback(rules, function(rule, cb) {
readRuleList(rule, cb, 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);
}
return joinPath(root, decodePath(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);
}
filePath = getTempFilePath(filePath, rule);
if (!filePath) {
return callback();
}
readFile = fileMgr[needRawData ? 'readFile' : 'readFileText'];
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) {
if (isJson) {
var val = removeProtocol(getMatcher(rule), true);
val = val && val.trim();
var json = getMatcherJson(rule, val) || parsePureJSON(val, QUERY_PARAM_RE.test(val));
if (json) {
return callback(json);
}
}
return readRuleValue(
rule,
isJson
? function (value) {
callback(tryParseMatcher(value, rule) || _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 tryParseMatcher(text, item) || _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 json = r.jsonObject;
var value;
if (json) {
r.jsonObject = undefined;
} else {
value = removeProtocol(getMatcher(r), true);
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();
}
handleCallback(rules, function(rule, cb) {
readRuleList(rule, cb, 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) {
if (rule.key) {
return [];
}
var files = rule.files || [getPath(getUrl(rule))];
var rawFiles = rule.rawFiles || files;
var result = [];
files.forEach(function (file, i) {
var opts = pluginMgr.resolveKey(rawFiles[i], rule, req);
if (opts) {
result.push(opts);
} else if (file = getTempFilePath(file, rule)) {
file = fileMgr.convertSlash(file);
if (END_SLASH_RE.test(file)) {
result.push(file.slice(0, -1));
result.push(file + 'index.html');
} else {
result.push(file);
}
} else {
result.push(file);
}
});
return result;
};
exports.getWriteFilePath = function(rule) {
var filePath = getPath(getUrl(rule));
return filePath && joinPath(rule.root, decodePath(filePath));
};
function getUrl(rule) {
return common.trimUrl(rule && (common.getRuleValue(rule) || rule.url));
}
exports.rule = {
getMatcher: getMatcher,
getUrl: getUrl
};
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 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';
};
exports.getZipStream = getZipStream;
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 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, clientIpKey);
var cipKey = config.cipKey;
if (cipKey && (!ip || config.overCipKey)) {
ip = getClientIpFH(headers, cipKey) || ip;
}
return ip;
}
exports.getForwardedFor = getForwardedFor;
function getClientIp(req, ip) {
ip = ip || getForwardedFor(req.headers || {}) || getRemoteAddr(req);
return isLocalIp(ip) ? LOCALHOST : ip;
}
exports.getClientIp = getClientIp;
function getClientPort(req) {
return common.getClientPort(req, config);
}
exports.getClientPort = getClientPort;
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);
};
var MULTIPART_RE = /multipart/i;
function isMultipart(req) {
return MULTIPART_RE.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 = function (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 = {};
}
Object.keys(obj).forEach(function (key) {
data.headers[key] = obj[key];
});
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 joinPath(root, dir) {
if (common.existsUpPath(dir)) {
return;
}
if (!root) {
return dir;
}
var fullPath = path.resolve(root, dir);
var slash = END_SLASH_RE.exec(dir || root);
return slash && !END_SLASH_RE.test(fullPath) ? fullPath + slash[0] : fullPath;
}
exports.joinPath = joinPath;
function parseRuleProps(list, result) {
result = result || {};
if (list) {
list.forEach(function(rule) {
if (rule = getMatcherValue(rule)) {
common.parseProps(rule).forEach(function (action) {
result[action] = true;
});
}
});
}
return result;
}
exports.parseRuleProps = parseRuleProps;
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]) {