whistle
Version:
HTTP, HTTP2, HTTPS, Websocket debugging proxy
1,781 lines (1,709 loc) • 68.5 kB
JavaScript
var parseUrl = require('../util/parse-url-safe');
var net = require('net');
var extend = require('extend');
var crypto = require('crypto');
var util = require('../util');
var rulesUtil = require('./util');
var lookup = require('./dns');
var protoMgr = require('./protocols');
var config = require('../config');
var rules = rulesUtil.rules;
var values = rulesUtil.values;
var env = process.env || {};
var allowDnsCache = true;
var SUB_MATCH_RE = /\$[&\d]/;
var BODY_MATCH_RE = /\$b[&\d]/;
var SPACE_RE = /\s+/g;
var EXACT_RE = /^\$/;
var NON_RE = /^!/;
var HAS_SPACE_RE = /\s/;
var MULTI_TO_ONE_RE = /^\s*line`\s*[\r\n]([\s\S]*?)[\r\n]\s*`\s*?$/gm;
var WEB_PROTOCOL_RE = /^(?:https?|wss?|tunnel):\/\//;
var PORT_RE = /^(x?hosts?:\/\/)(?:([\w.-]*)|\[([:\da-f.]+)\])(?::(\d+))?$/i;
var PLUGIN_RE = /^(?:plugin|whistle)\.([a-z\d_\-]+:\/\/[\s\S]*)/;
var PLUGIN_TPL_RE = /(^|\s)?(%[a-z\d_-]+(?:\.[^\s=]+)?=|(?:whistle\.)?[a-z\d_-]+:\/\/)([^\s]*)/g;
var ROOT_PLUGIN_RE = /[\\/]whistle\.([a-z\d_-]+)$/;
var TPL_KEY_RE = /\{\{\s*%?ruleValue\s*\}\}/g;
var END_RE = /\$$/;
var protocols = protoMgr.protocols;
var protocolsWithoutG = protoMgr.protocolsWithoutG;
var reqProtocols = protoMgr.reqProtocols;
var reqProtosWithoutG = protoMgr.reqProtosWithoutG;
var pureResProtocols = protoMgr.pureResProtocols;
var multiMatchs = protoMgr.multiMatchs;
var aliasProtocols = protoMgr.aliasProtocols;
var FILE_RE = /^(?:[a-z]:(?:\\|\/[^/])|\/[^/])/i;
var PROXY_RE =
/^x?(socks|proxy|https?-proxy|internal-proxy|internal-https?-proxy|https2http-proxy|http2https-proxy):\/\//;
var VAR_RE = /\${([^{}]+)}/g;
var NO_SCHEMA_RE = /^\/\/[^/]/;
var WILDCARD_RE = /^(\$?((?:[a-z*]+):\/\/)?([^/?]*))/;
var RULE_KEY_RE = /^\$\{(\S+)\}$/;
var VALUE_KEY_RE = /^\{(\S+)\}$/;
var LINE_END_RE = /\n|\r\n|\r/;
var LOCAL_RULE_RE =
/^https?:\/\/local\.(?:whistlejs\.com|wproxy\.org)(:realPort)?(?:\/|\?|$)/;
var PATH_RE = /^<.*>$/;
var VALUE_RE = /^\(.*\)$/;
var REG_URL_RE = /^((?:[a-z*]+:)?\/\/)?([^/?]*)/;
var LIKE_REG_URL_RE = /^(?:(?:(?:https?|wss?|tunnel):)?\/\/)?\*+\/[^?*]*\*/;
var LIKE_REG_URL_RE2 = /^(?:(?:(?:https?|wss?|tunnel):)?\/\/)?\.[^./?]+\.[^/?]+\/[^?*]*\*/;
var DOT_DOMAIN_RE = /^\.[^./?]+\.[^/?]/;
var REG_URL_SYMBOL_RE = /^(\^+)/;
var PATTERN_FILTER_RE = /^(?:filter|ignore):\/\/(.+)\/(i)?$/;
var LINE_PROPS_RE = /^lineProps:\/\/(.*)$/;
var FILTER_RE = /^(?:excludeFilter|includeFilter):\/\/(.*)$/;
var PROPS_FILTER_RE =
/^(?:filter|excludeFilter|includeFilter|ignore):\/\/(m(?:ethod)?|i(?:p)?|h(?:eader)?|env|s(?:tatusCode)?|from|b(?:ody)?|clientIp|clientIP|clientPort|remoteAddress|remotePort|serverIp|serverIP|serverPort|chance|probability|re[qs](?:H(?:eaders?)?)?):(.+)$/;
var PURE_FILTER_RE =
/^(?:excludeFilter|includeFilter):\/\/(statusCode|from|env|clientIp|clientIP|clientPort|remoteAddress|remotePort|serverIp|serverIP|chance|probability|serverPort|host|re[qs](?:H(?:eaders?)?)?)[.=](.+)$/;
var PATTERN_WILD_FILTER_RE = /^(?:filter|ignore):\/\/(!)?(\*+\/)/;
var INLINE_RE = /^((?:filter|excludeFilter|includeFilter|ignore):\/\/)(\(.*\)|\<.*\>)$/;
var CHROME_PATH_RE = /^file:\/\/\/[A-Z]:\//;
var AT_RE = /^@/;
var WILD_FILTER_RE = /^(\*+\/)/;
var regUrlCache = {};
var hostCache = {};
var NON_STAR_RE = /[^*]/;
var DOMAIN_STAR_RE = /([*~]+)(\\.)?/g;
var STAR_RE = /\*+/g;
var PORT_PATTERN_RE = /^!?:\d{1,5}$/;
var COMMENT_RE = /#[^\r\n]*/g;
var TPL_RE = /^((?:[\w.-]+:)?\/\/)?(`.*`)$/;
// url: protocol, host, port, hostname, search, query, pathname, path, href, query.key
// req|res: ip, method, statusCode, headers?.key, cookies?.key
var PLUGIN_NAME_RE =
/^[a-z\d_\-]+(?:\.g?(?:all)?var(?:\$|\d*))?(?:\.replace\(.+\))?$/i;
var VAR_INDEX_RE = /^([a-z\d_\-]+)\.(g)?(all)?var(\$|\d*)/i;
var TPL_VAR_RE =
/(\$)?\$\{(\{)?(id|reqId|whistle|env|now|random(?:Int\(\d{1,15}(?:-\d{1,15})?\))?|randomUUID|host|port|realPort|realHost|realUrl|version|url|hostname|query|search|queryString|searchString|path|pathname|clientId|localClientId|ip|clientIp|clientPort|remoteAddress|remotePort|serverIp|serverPort|method|status(?:Code)|reqCookies?|resCookies?|re[qs]H(?:eaders?)?)(?:\.([^{}]+))?\}(\})?/gi;
var RANDOM_RE = /randomInt\((\d{1,15})(?:-(\d{1,15}))?\)/i;
var REPLACE_PATTERN_RE = /(^|\.)replace\((.+)\)$/i;
var SEP_RE = /^[?/]/;
var COMMA1_RE = /\\,/g;
var COMMA2_RE = /\\\\,/g;
var G_CR_RE = /\r/g;
var G_LF_RE = /\n/g;
var SUFFIX_RE = /^\\\.[\w-]+$/;
var DOT_PATTERN_RE = /^\.[\w-]+(?:[?$]|$)/;
var inlineValues = {};
var pluginMgr;
var ENABLE_PROXY_RE =
/\bproxy(?:Host|First|Tunnel)|clientId|multiClient|singleClient\b/i;
var PLUGIN_VAR_RE = /^%([a-z\d_\-]+)([=.])([^\s]*)/;
var EXACT_IGNORE_RE = /^ignore:\/\/(pattern|matcher|operator|operation)[=:](.+)$/;
var EXACT_SKIP_RE = /^(pattern|matcher|operator|operation)[=:](.+)$/;
var FILE_PROTO_RE = /^x?((raw)?file|tpl|jsonp|dust):\/\//;
var NO_PROTO_RE = /[^\w!*|.-]/;
var SKIP_RE = /^skip:\/\//;
var SUB_VAR_RE = /\$\{RegExp\.\$([&\d])\}/g;
var TOOL_RE = /^(?:log|weinre):\/\//;
var mIndex = 0;
var mNow = Date.now();
var IS_JSON = Symbol('isJson');
function getMFlag() {
if (mIndex === Number.MAX_SAFE_INTEGER) {
mIndex = 0;
mNow = Date.now();
}
return mNow + '-' + (mIndex++);
}
function removeComment(text) {
return text.replace(COMMENT_RE, '').trim();
}
function domainToRegExp(all, star, dot) {
var len = star.length;
var result = len > 1 ? '([^/?]*)' : '([^/?.]*)';
if (dot) {
result += '\\.';
if (len > 2) {
result = '(?:' + result + ')?';
}
}
return result;
}
function pathToRegExp(all) {
var len = all.length;
if (len > 2) {
return '(.*)';
}
return len > 1 ? '([^?]*)' : '([^?/]*)';
}
function queryToRegExp(all) {
return all.length > 1 ? '(.*)' : '([^&]*)';
}
function isRegUrl(url, isCheck) {
var result = regUrlCache[url];
if (result) {
return isCheck ? result : extend({}, result);
}
var oriUrl = url;
var not = isNegativePattern(url);
if (not) {
url = url.substring(1);
}
if (DOT_PATTERN_RE.test(url)) {
url = '^' + url;
}
var hasStartSymbol = REG_URL_SYMBOL_RE.test(url);
var hasEndSymbol, ignoreCase, startWithDot;
if (hasStartSymbol) {
ignoreCase = RegExp.$1.length;
url = url.substring(ignoreCase);
hasEndSymbol = END_RE.test(url);
if (hasEndSymbol) {
url = url.slice(0, -1);
}
ignoreCase = ignoreCase === 1;
} else {
startWithDot = LIKE_REG_URL_RE2.test(url);
if (startWithDot || LIKE_REG_URL_RE.test(url)) {
ignoreCase = hasStartSymbol = true;
}
}
if (!hasStartSymbol || !REG_URL_RE.test(url)) {
return false;
}
var protocol = RegExp.$1 || '';
var domain = RegExp.$2;
var pathname = url.substring(protocol.length + domain.length);
var query = '';
var index = pathname.indexOf('?');
if (index !== -1) {
query = pathname.substring(index);
pathname = pathname.substring(0, index);
}
if (!protocol || protocol === '//') {
protocol = '[a-z]+://';
} else {
protocol = util.escapeRegExp(protocol).replace(/\*+/, '([a-z:]*)');
}
if (startWithDot) {
domain = domain.substring(1);
}
domain = util.escapeRegExp(domain);
if (domain.length > 2 && !NON_STAR_RE.test(domain)) {
domain = '([^?]*)';
} else if (domain) {
domain = domain.replace(DOMAIN_STAR_RE, domainToRegExp);
} else {
domain = '[^/?]*';
}
if (startWithDot) {
domain = '(?:[^/?.]*.)?' + domain;
}
if (pathname) {
pathname = util.escapeRegExp(pathname).replace(STAR_RE, pathToRegExp);
} else if (hasStartSymbol && SUFFIX_RE.test(domain)) {
pathname = '/[^?]+' + domain + (hasEndSymbol || query ? '' : '(?:\\??.*)$');
domain = '[^/?]+';
} else if (query || hasEndSymbol) {
pathname = '/';
}
query =
pathname +
(query ? util.escapeRegExp(query).replace(STAR_RE, queryToRegExp) : '');
var pattern = '^' + protocol + domain + query + (hasEndSymbol ? '$' : '');
try {
result = regUrlCache[oriUrl] = {
not: not,
pattern: new RegExp(pattern, ignoreCase ? 'i' : '')
};
} catch (e) {}
return result;
}
function formatShorthand(url) {
if (NO_SCHEMA_RE.test(url)) {
return url;
}
if (url === 'includeFilter://safeHtml') {
return 'lineProps://safeHtml';
}
if (url === 'includeFilter://strictHtml') {
return 'lineProps://strictHtml';
}
if (
url === '{}' ||
VALUE_KEY_RE.test(url) ||
PATH_RE.test(url) ||
VALUE_RE.test(url)
) {
return 'file://' + url;
}
if (url === '/' || (FILE_RE.test(url) && !util.isRegExp(url))) {
return 'file://' + url;
}
// compact Chrome
if (CHROME_PATH_RE.test(url)) {
return 'file://' + url.substring(8);
}
if (AT_RE.test(url)) {
if (url.indexOf('@://') === -1) {
url = '@://' + url.substring(1);
}
return url.replace('@', 'G');
}
if (PLUGIN_VAR_RE.test(url)) {
return url.replace('%', 'P://');
}
return url;
}
function getKey(url) {
if (url.indexOf('{') == 0) {
var index = url.lastIndexOf('}');
return index > 1 && url.substring(1, index);
}
return false;
}
function getValue(url, start, end, lineProps) {
if (url.indexOf(start || '(') == 0) {
var len = url.length - 1;
if (url[len] === (end || ')')) {
return url.substring(1, len);
}
}
if (lineProps) {
if (lineProps[IS_JSON] == null) {
lineProps[IS_JSON] = util.isJson(url);
}
if (lineProps[IS_JSON]) {
return url;
}
}
return false;
}
function getFiles(path) {
return FILE_PROTO_RE.test(path) ? util.removeProtocol(path, true).split('|') : null;
}
function setProtocol(target, source) {
if (util.hasProtocol(target)) {
return target;
}
var protocol = util.getProtocol(source);
if (protocol == null) {
return target;
}
return protocol + (NO_SCHEMA_RE.test(target) ? '' : '//') + target;
}
function isPathSeparator(ch) {
return ch == '/' || ch == '\\' || ch == '?';
}
/**
* query1: xxxx, xxxx?, xxx?xxxx
* query2: ?xxx, xxx?xxxx
* @param query1
* @param query2
* @returns
*/
function joinQuery(query1, query2) {
if (!query1 || !query2) {
return query1 || query2;
}
query2 = query2.substring(1);
var firstLen = query1.length;
var secondLen = query2.length;
var sep =
firstLen < 2 ||
!secondLen ||
query1[firstLen - 1] === '&' ||
query2[0] === '&'
? ''
: '&';
return query1 + sep + query2;
}
function joinUrl(a, b) {
if (!a || !b) {
return a + b;
}
var firstIndex = a.indexOf('?');
var secondIndex = b.indexOf('?');
var firstQuery = '';
var secondQuery = '';
if (firstIndex != -1) {
firstQuery = a.substring(firstIndex);
a = a.substring(0, firstIndex);
}
if (secondIndex != -1) {
secondQuery = b.substring(secondIndex);
b = b.substring(0, secondIndex);
}
if (b) {
var lastIndex = a.length - 1;
var startWithSep = isPathSeparator(b[0]);
if (isPathSeparator(a[lastIndex])) {
a = startWithSep
? a.substring(0, lastIndex) + b
: a + b;
} else {
a = a + (startWithSep ? '' : '/') + b;
}
}
var query = joinQuery(firstQuery, secondQuery);
return WEB_PROTOCOL_RE.test(a) ? util.formatUrl(a + query) : a + query;
}
function toLine(_, line) {
return line.replace(SPACE_RE, ' ');
}
function mergeLines(text) {
return removeComment(text).replace(MULTI_TO_ONE_RE, toLine);
}
function parsePluginTpl(lines) {
var ruleTpls = pluginMgr && pluginMgr.ruleTpls;
if (!ruleTpls) {
return lines;
}
var result = [];
lines.forEach(function(line) {
line = removeComment(line);
var isPrivate = HAS_SPACE_RE.test(line);
line = line.replace(PLUGIN_TPL_RE, function(all, sep, key, value) {
var isVar = key.indexOf('=') !== -1;
if (isVar && isPrivate) {
key = '_' + key;
}
var tpl = ruleTpls[key.slice(0, isVar ? -1 : -3)];
if (tpl == null) {
return all;
}
return (sep || '') + mergeLines(tpl).replace(TPL_KEY_RE, function(key) {
return key.indexOf('%') === -1 ? value : util.encodeURIComponent(value);
});
});
result.push.apply(result, mergeLines(line).split(LINE_END_RE));
});
return result;
}
function getLines(text, root) {
if (!text || !(text = text.trim())) {
return [];
}
text = mergeLines(text);
var ruleKeys = {};
var valueKeys = {};
var lines = text.split(LINE_END_RE);
var result = [];
lines.forEach(function (line) {
line = line.trim();
if (!line) {
return;
}
var ruleList;
var isRuleKey = RULE_KEY_RE.test(line);
if (isRuleKey || VALUE_KEY_RE.test(line)) {
if (root) {
var key = RegExp.$1;
line = '';
if (isRuleKey) {
if (!ruleKeys[key]) {
ruleKeys[key] = 1;
line = rules.get(key);
}
} else if (!valueKeys[key]) {
valueKeys[key] = 1;
line = values.get(key);
}
ruleList = line && mergeLines(line).split(LINE_END_RE);
}
} else {
ruleList = [line];
}
if (ruleList) {
ruleList = parsePluginTpl(ruleList);
result.push.apply(result, ruleList);
}
});
return result;
}
function resolvePropValue(obj, key) {
return (key && obj && obj[key.toLowerCase()]) || '';
}
function resolveUrlVar(req, key, escape) {
var url = req.fullUrl || req.curUrl;
if (!key) {
return url;
}
var options = req.__options || req.options;
if (!options || options.href !== url) {
options = req.__options = util.parseUrl(url);
req.__query = req.__query$ = '';
}
if (key.indexOf('query.') !== 0 || !options.query) {
if (key === 'actualPort' || key === 'realPort') {
return options['port'] || (options.protocol === 'https:' || options.protocol === 'wss:' ? '443' : '80');
}
return options[key] || '';
}
var queryKey = '__query' + (escape ? '$' : '');
var query = req[queryKey];
if (!query) {
query = req[queryKey] = util.parseQuery(options.query, null, null, escape);
}
return util.getQueryValue(query[key.substring(6)]);
}
function resolveReqCookiesVar(req, key, escape) {
var cookie = req.headers.cookie || '';
if (!cookie || !key) {
return cookie;
}
var cookies = req.__cookies;
if (!cookies || req.__rawCookies !== cookie) {
req.__rawCookies = cookie;
cookies = req.__cookies = util.parseQuery(cookie, '; ', null, escape);
}
return util.getQueryValue(cookies[key]);
}
function resolveResCookiesVar(req, key) {
var resHeaders = req.resHeaders;
var cookie = resHeaders && resHeaders['set-cookie'];
var isArray = Array.isArray(cookie);
if (!isArray && cookie) {
isArray = true;
cookie = [String(cookie)];
}
if (!isArray) {
return cookie || '';
}
var rawCookie = cookie.join(', ');
if (!key || !rawCookie) {
return rawCookie;
}
var cookies = req.__resCookies;
if (!cookies || req.__rawResCookies !== rawCookie) {
req.__rawResCookies = cookie.join();
cookies = req.__resCookies = {};
cookie.forEach(function (c) {
c = util.parseQuery(c, '; ', null, escape);
Object.keys(c).forEach(function (key) {
var item = {};
switch (key.toLowerCase()) {
case 'domain':
item.domain = c[key];
break;
case 'path':
item.path = c[key];
break;
case 'expires':
item.expires = c[key];
break;
case 'max-age':
item.maxAge = item['max-age'] = item['Max-Age'] = c[key];
break;
case 'httponly':
item.httpOnly = true;
break;
case 'secure':
item.secure = true;
break;
case 'samesite':
item.samesite = item.sameSite = item.SameSite = c[key];
break;
case 'partitioned':
item.partitioned = true;
break;
default:
if (!cookies[key]) {
item.value = c[key];
cookies[key] = item;
}
}
});
});
}
var index = key.indexOf('.');
var name;
if (index !== -1) {
name = key.substring(index + 1);
key = key.substring(0, index);
}
cookie = cookies[key];
if (!cookie) {
return '';
}
return (name ? cookie[name] : cookie.value) || '';
}
function resolveServerIpVar(req, key) {
if (!req.resHeaders) {
return '';
}
return req.hostIp || '127.0.0.1';
}
function resolveResHeadersVar(req, key) {
return resolvePropValue(req.resHeaders, key);
}
function getPluginVar(vars, index) {
if (!vars) {
return '';
}
if (vars && index === '$') {
index = vars.length - 1;
}
return (vars && vars[index || 0]) || '';
}
function resolveRuleValue(req, key) {
var curRules = key && req.rules;
if (curRules) {
if (VAR_INDEX_RE.test(key)) {
var shortName = RegExp.$1;
var isGlobal = RegExp.$2;
var isAll = RegExp.$3;
var index = RegExp.$4 || 0;
var gVars = req._globalPluginVars && req._globalPluginVars[shortName];
var vars = req._pluginVars && req._pluginVars[shortName];
if (isAll) {
if (vars && gVars) {
vars = isGlobal ? gVars.concat(vars) : vars.concat(gVars);
}
return getPluginVar(vars || gVars, index);
}
return getPluginVar(isGlobal ? gVars : vars, index);
}
var plugin = curRules.plugin;
var matcher;
key = key + '://';
if (plugin) {
var list = Array.isArray(plugin.list) ? plugin.list : [plugin];
var name = 'whistle.' + key;
for (var i = 0, len = list.length; i < len; i++) {
matcher = list[i].matcher;
if (!matcher.indexOf(name)) {
return matcher.substring(name.length);
}
}
}
matcher = curRules.rule && curRules.rule.matcher;
if (matcher && !matcher.indexOf(key)) {
return matcher.substring(key.length);
}
}
return '';
}
function resolveVarValue(req, escape, name, key) {
var lname = name.toLowerCase();
if (RANDOM_RE.test(lname)) {
var min = 0;
var max = 0;
if (RegExp.$2) {
min = parseInt(RegExp.$1, 10);
max = parseInt(RegExp.$2, 10);
if (max < min) {
var temp = max;
max = min;
min = temp;
}
max = max - min;
} else if (RegExp.$1) {
max = parseInt(RegExp.$1, 10);
}
return max && (Math.floor(Math.random() * (max + 1)) + min);
}
switch (lname) {
case 'now':
return Date.now();
case 'random':
return Math.random();
case 'randomuuid':
return crypto.randomUUID ? crypto.randomUUID() : '';
case 'id':
case 'reqid':
return req.reqId || '';
case 'whistle':
return resolveRuleValue(req, key);
case 'path':
case 'pathname':
case 'search':
return key ? '' : resolveUrlVar(req, lname, escape);
case 'querystring':
case 'searchstring':
return key ? '' : resolveUrlVar(req, 'search', escape) || '?';
case 'query':
key = key ? 'query.' + key : 'query';
return resolveUrlVar(req, key, escape);
case 'url':
return resolveUrlVar(req, key, escape);
case 'port':
return config.port;
case 'host':
return config.host || '';
case 'realport':
return config.realPort || config.port;
case 'realhost':
return config.realHost || config.host || '';
case 'realurl':
return req.realUrl && (req.realUrl !== req.fullUrl) ? req.realUrl : '';
case 'version':
return config.version;
case 'reqcookie':
case 'reqcookies':
return resolveReqCookiesVar(req, key, escape);
case 'rescookie':
case 'rescookies':
return resolveResCookiesVar(req, key, escape);
case 'method':
return req.method;
case 'ip':
case 'clientip':
return req.clientIp;
case 'clientid':
return req._origClientId || util.getClientId(req.headers);
case 'clientport':
return req.clientPort || '';
case 'localclientid':
return config.clientId;
case 'statuscode':
case 'status':
return req.statusCode || '';
case 'serverip':
return resolveServerIpVar(req, key);
case 'serverport':
return req.serverPort || '';
case 'reqh':
case 'reqheader':
case 'reqheaders':
return resolvePropValue(req.headers, key);
case 'hostname':
return util.hostname();
case 'remoteaddress':
return req._remoteAddr || '';
case 'remoteport':
return req._remotePort || '0';
case 'env':
return (key && env[key]) || '';
default:
return resolveResHeadersVar(req, key);
}
}
function resetComma(str) {
return str && str.replace(G_CR_RE, ',').replace(G_LF_RE, '\\,');
}
function resolveTplVar(value, req) {
return value.replace(TPL_VAR_RE, function (all, escape, lb, name, key, rb) {
if (
(lb && !rb) ||
(name === 'whistle' && (!key || !PLUGIN_NAME_RE.test(key)))
) {
return all;
}
var pattern, regPattern;
var replacement = '';
if (REPLACE_PATTERN_RE.test(key)) {
pattern = RegExp.$2;
var dot = RegExp.$1 || '';
key = key.substring(0, key.length - 9 - dot.length - pattern.length);
if (pattern.indexOf(',') !== -1) {
pattern = pattern.replace(COMMA2_RE, '\n').replace(COMMA1_RE, '\r');
var index = pattern.indexOf(',');
if (index !== -1) {
replacement = resetComma(pattern.substring(index + 1));
pattern = pattern.substring(0, index);
}
pattern = resetComma(pattern);
}
regPattern = util.toOriginalRegExp(pattern);
}
var val = resolveVarValue(req, escape, name, key);
if (typeof val !== 'string') {
val = val == null ? '' : val + '';
}
if (!val || !pattern) {
val = pattern ? val : val || replacement;
} else if (!regPattern || !SUB_MATCH_RE.test(replacement)) {
val = val.replace(regPattern || pattern, replacement);
} else {
val = val.replace(regPattern, function () {
return util.replacePattern(replacement, arguments);
});
}
if (val && lb) {
val = util.encodeURIComponent(val);
}
return val + (!lb && rb ? '}' : '');
});
}
Rules.resolveTplVar = resolveTplVar;
function renderTpl(rule, req) {
var matcher = rule.matcher;
if (rule.isTpl === false) {
return matcher;
}
rule.isTpl = false;
return matcher.replace(TPL_RE, function (_, proto, value) {
rule.isTpl = true;
return (proto || '') + resolveTplVar(value.slice(1, -1), req);
});
}
function resolveVar(rule, vals, req) {
var matcher = renderTpl(rule, req);
return matcher.replace(VAR_RE, function (all, key) {
key = getValueFor(key, vals, rule.file);
if (typeof key === 'string') {
return rule.isTpl && key ? resolveTplVar(key, req) : key;
}
return all;
});
}
function getValueFor(key, vals, file) {
if (!key) {
return;
}
var key1 = util.getInlineKey(key, file);
var val = vals ? vals[key1] : undefined;
if (val !== undefined) {
val = vals[key1] = val && typeof val == 'object' ? JSON.stringify(val) : val;
} else {
val = values.get(key);
}
return val;
}
function getRule(req, list, vals, index, isFilter, host, isReq) {
var rule = resolveRuleList(req, list, vals, index || 0, isFilter, null, host, isReq);
resolveValue(rule, vals, req);
return rule;
}
function resolveValue(rule, vals, req) {
if (!rule) {
return;
}
var matcher = rule.matcher;
var index = matcher.indexOf('://') + 3;
var protocol = matcher.substring(0, index);
var regExp = rule.regExp;
delete rule.regExp;
matcher = matcher.substring(index);
var key = getKey(matcher);
if (key) {
rule.key = key;
}
var value = getValueFor(key, vals, rule.file);
if (value == null) {
value = getValue(matcher, null, null, rule.lineProps);
regExp = null;
}
if (value !== false) {
var val = setProtocol(protocol + value, req.curUrl);
if (rule.isTpl && regExp) {
val = val.replace(SUB_VAR_RE, function(_, index) {
index = index === '&' ? 0 : index;
return regExp[index] || '';
});
}
if (rule.isTpl) {
val = resolveTplVar(val, req);
}
if (protocol === 'style://') {
rule.value = val.substring(0, 128);
} else {
Object.defineProperty(rule, 'value', { value: val });
}
} else if (!key && (value = getValue(matcher, '<', '>')) !== false) {
rule.path = setProtocol(protocol + value, req.curUrl);
rule.files = getFiles(rule.path);
}
return rule;
}
function getRelativePath(pattern, url, matcher) {
var index = url.indexOf('?');
if (index === -1 || pattern.indexOf('?') !== -1) {
return '';
}
if (matcher.indexOf('?') === -1) {
return url.substring(index);
}
url = url.substring(index + 1);
return (url && '&') + url;
}
function removeFilters(rule) {
var filters = rule.filters;
if (filters) {
if (filters.curFilter) {
rule.filter = filters.curFilter;
}
delete rule.filters;
}
}
function replaceSubMatcher(url, regExp, req) {
var vals = req._bodySubVals;
req._bodySubVals = undefined;
if ((!regExp || !SUB_MATCH_RE.test(url)) && (!vals || !BODY_MATCH_RE.test(url))) {
return url;
}
return util.replacePattern(url, regExp, vals);
}
var PROTOS = {
http: 1,
https: 1,
tunnel: 1,
ws: 1,
wss: 1
};
function removePort(url) {
var index = url.indexOf('://');
if (index === -1) {
return url;
}
var protocol = url.substring(0, index);
if (!PROTOS[protocol]) {
return url;
}
index += 3;
var urlPath = '';
var end = url.indexOf('/', index);
if (end !== -1) {
urlPath = url.substring(end);
url = url.substring(0, end);
}
end = url.indexOf(':', index);
if (end !== -1) {
url = url.substring(0, end);
}
return url + urlPath;
}
function checkInternal(req, rule) {
var props = rule.lineProps;
if (req._isInternalReq) {
return !props.internal && !props.internalOnly;
}
return props.internalOnly;
}
function resolveRuleList(req, list, vals, index, isFilter, isEnableProxy, host, isReq) {
var curUrl = util.formatUrl(req.curUrl);
var notHttp = list.isRuleProto && curUrl[0] !== 'h';
//支持域名匹配
var domainUrl = removePort(curUrl);
var hasIndex = typeof index === 'number';
index = hasIndex ? index : -1;
var results = [];
var url = util.getPureUrl(curUrl);
var _domainUrl = util.getPureUrl(domainUrl);
var rule, matchedUrl, files, matcher, result, origMatcher, filePath;
var setMatcher = function(matcher) {
result.matcher = matcher;
var _matcher = rule ? rule.matcher : undefined;
if (_matcher !== matcher) {
result._matcher = _matcher;
}
};
var getPathRule = function () {
result = extend(
{
files: files,
url: joinUrl(matcher, filePath)
},
rule
);
if (files && filePath) {
result.files = files.map(function (file) {
return joinUrl(file, filePath);
});
result.rawFiles = files;
}
setMatcher(origMatcher);
removeFilters(result);
if (hasIndex) {
return result;
}
results.push(result);
};
var getExactRule = function (relPath, regObj) {
origMatcher = resolveVar(rule, vals, req);
origMatcher = replaceSubMatcher(origMatcher, regObj, req);
matcher = setProtocol(origMatcher, curUrl);
result = extend(
{
files: getFiles(matcher),
url: matcher + relPath
},
rule
);
setMatcher(origMatcher);
removeFilters(result);
if (hasIndex) {
return result;
}
results.push(result);
};
var checkFilter = function () {
req._bodySubVals = undefined;
if (notHttp && protoMgr.isFileProxy(rule.matcher)) {
return false;
}
if (isReq && TOOL_RE.test(rule.matcher)) {
return true;
}
return (isFilter || !matchExcludeFilters(curUrl, rule, req)) && (host == null || util.checkProxyHost(rule, host));
};
for (var i = 0; (rule = list[i]); i++) {
if ((isEnableProxy && !ENABLE_PROXY_RE.test(rule.matcher)) || checkInternal(req, rule) ||
(req._skipProps && (util.exactIgnore(req._skipProps, rule) || util.checkSkip(req._skipProps, rule, curUrl)))) {
continue;
}
var pattern = rule.isRegExp
? rule.pattern
: setProtocol(rule.pattern, curUrl);
var not = rule.not;
var matchedRes;
if (rule.isRegExp) {
matchedRes = pattern.test(curUrl);
matchedRes = not ? !matchedRes : matchedRes;
var regExp;
if (matchedRes) {
regExp = {};
if (!not) {
for (var j = 1; j < 10; j++) {
regExp[j] = RegExp['$' + j];
}
}
}
if (matchedRes && checkFilter() && --index < 0) {
regExp['0'] = curUrl;
matcher = resolveVar(rule, vals, req);
// 支持 $x 包含 `|` 的情形
matcher = setProtocol(replaceSubMatcher(matcher, regExp, req), curUrl);
files = getFiles(matcher);
result = extend({ url: matcher, files: files }, rule);
setMatcher(matcher);
result.regExp = regExp;
removeFilters(result);
if (hasIndex) {
return result;
}
results.push(result);
}
} else if (rule.wildcard) {
var wildcard = rule.wildcard;
var matched = wildcard.preMatch.exec(curUrl);
if (matched && checkFilter()) {
var regObj = {};
for (var k = 0; k < 9; k++) {
regObj[k] = matched[k + 1] || '';
}
filePath = curUrl.substring(regObj[0].length);
var wPath = wildcard.path;
if (wildcard.isExact) {
if (
(filePath === wPath || util.getPureUrl(filePath) === wPath) &&
--index < 0
) {
if (
(result = getExactRule(
getRelativePath(wPath, filePath, rule.matcher),
regObj
))
) {
return result;
}
}
} else if (filePath.indexOf(wPath) === 0) {
var wpLen = wPath.length;
filePath = filePath.substring(wpLen);
if (
(wildcard.hasQuery ||
!filePath ||
wPath[wpLen - 1] === '/' ||
SEP_RE.test(filePath)) &&
--index < 0
) {
origMatcher = resolveVar(rule, vals, req);
origMatcher = replaceSubMatcher(origMatcher, regObj, req);
matcher = setProtocol(origMatcher, curUrl);
files = getFiles(matcher);
if (wildcard.hasQuery && filePath) {
filePath = '?' + filePath;
}
if ((result = getPathRule())) {
return result;
}
}
}
}
} else if (rule.isExact) {
matchedRes = pattern === url || pattern === curUrl;
if ((not ? !matchedRes : matchedRes) && checkFilter() && --index < 0) {
if (
(result = getExactRule(
getRelativePath(pattern, curUrl, rule.matcher)
))
) {
return result;
}
}
} else if (
((matchedUrl = curUrl.indexOf(pattern) === 0) ||
(rule.isDomain && domainUrl.indexOf(pattern) === 0)) &&
checkFilter()
) {
var len = pattern.length;
origMatcher = resolveVar(rule, vals, req);
origMatcher = replaceSubMatcher(origMatcher, null, req);
matcher = setProtocol(origMatcher, curUrl);
files = getFiles(matcher);
var hasQuery = pattern.indexOf('?') !== -1;
if (
(hasQuery ||
(matchedUrl
? pattern == url || isPathSeparator(url[len])
: pattern == _domainUrl || isPathSeparator(_domainUrl[len])) ||
isPathSeparator(pattern[len - 1])) &&
--index < 0
) {
filePath = (matchedUrl ? curUrl : domainUrl).substring(len);
if (hasQuery) {
if (filePath) {
filePath = '?' + filePath;
}
} else if (rule.isDomain && rule.lineProps.originUrl) {
filePath = '/';
}
if ((result = getPathRule())) {
return result;
}
}
}
}
return hasIndex ? null : results;
}
function resolveProps(req, rules, vals, isIgnore) {
var list = this.getRuleList(req, rules, vals);
var result = {};
if (isIgnore) {
list = list.filter(function(rule) {
var matcher = rule.matcher;
if (SKIP_RE.test(matcher)) {
matcher = matcher.slice(7);
if (!matcher) {
return false;
}
if (EXACT_SKIP_RE.test(matcher) || NO_PROTO_RE.test(matcher)) {
var prop ='ignore|' + (RegExp.$1 === 'pattern' ? 'pattern' : 'matcher') + '=' + (RegExp.$2 || matcher);
req._skipProps = req._skipProps || {};
result[prop] = true;
req._skipProps[prop] = true;
return false;
}
matcher.split('|').forEach(function(name) {
if (name) {
req._skipProps = req._skipProps || {};
req._skipProps[name] = true;
}
});
} else if (EXACT_IGNORE_RE.test(matcher)) {
result['ignore|' + (RegExp.$1 === 'pattern' ? 'pattern' : 'matcher') + '=' + RegExp.$2] = true;
return false;
}
return true;
});
if (!list.length) {
return result;
}
}
return util.parseRuleProps(list, result);
}
function parseWildcard(pattern, not) {
if (!WILDCARD_RE.test(pattern)) {
return;
}
var preMatch = RegExp.$1;
var protocol = RegExp.$2;
var domain = RegExp.$3;
var startWithDot = DOT_DOMAIN_RE.test(domain);
if (
!startWithDot &&
protocol.indexOf('*') === -1 &&
domain.indexOf('*') === -1 &&
domain.indexOf('~') === -1
) {
return;
}
if (not) {
return false;
}
var restPath = pattern.substring(preMatch.length);
var path = restPath || '/';
var isExact = preMatch.indexOf('$') === 0;
if (isExact) {
preMatch = preMatch.substring(1);
}
var index = path.indexOf('?');
var hasQuery = index !== -1;
if (hasQuery && index === 0) {
path = '/' + path;
}
var dLen = domain.length;
var allowMatchPath = dLen > 2 && !NON_STAR_RE.test(domain);
if (allowMatchPath) {
preMatch = '[^?]*';
} else {
if (
!startWithDot &&
(domain === '*' || domain === '~') &&
path.charAt(0) === '/'
) {
preMatch += '*';
}
preMatch = util
.escapeRegExp(preMatch)
.replace(DOMAIN_STAR_RE, domainToRegExp);
if (dLen && domain[dLen - 1] !== '*' && domain.indexOf(':') === -1) {
preMatch += '(?::\\d+)?';
}
if (startWithDot) {
preMatch = preMatch.replace('\\.', '(?:[^/?.]*\\.)?');
}
}
if (!protocol) {
preMatch = '[a-z]+://' + preMatch;
} else if (protocol === '//') {
preMatch = '[a-z]+:' + preMatch;
}
preMatch =
'^(' + preMatch + (restPath ? '' : '[^/?]*') + ')' + (allowMatchPath ? util.escapeRegExp(path, true) : '');
return {
preMatch: new RegExp(preMatch),
path: path,
hasQuery: hasQuery,
isExact: isExact
};
}
function parseRule(rulesMgr, pattern, matcher, raw, root, options, file) {
if (isNegativePattern(matcher)) {
return;
}
var regUrl = regUrlCache[pattern];
var rawPattern = pattern;
var rawMatcher = matcher;
var noSchema;
var isRegExp, not, port, protocol, isExact;
if (regUrl) {
not = regUrl.not;
isRegExp = true;
pattern = regUrl.pattern;
} else {
not = isNegativePattern(pattern);
// 位置不能变
var isPortPattern = PORT_PATTERN_RE.test(pattern);
if (not) {
pattern = pattern.substring(1);
}
if (NO_SCHEMA_RE.test(pattern)) {
noSchema = true;
pattern = pattern.substring(2);
}
if (!pattern) {
return;
}
if (isPortPattern) {
isRegExp = true;
pattern = new RegExp('^[\\w]+://[^/?]+' + pattern + '/');
}
if (
!isRegExp &&
(isRegExp = util.isRegExp(pattern)) &&
!(pattern = util.toRegExp(pattern))
) {
return;
}
if (!isRegExp) {
var wildcard = parseWildcard(pattern, not);
if (wildcard === false) {
return;
}
if (!wildcard && isExactPattern(pattern)) {
isExact = true;
pattern = pattern.slice(1);
} else if (not) {
return;
}
}
}
var proxyName, isRules, isSpec;
if (isHost(matcher)) {
matcher = 'host://' + matcher;
protocol = 'host';
} else if (matcher[0] === '/') {
if (matcher[1] === '/') {
protocol = 'rule';
} else {
matcher = 'file://' + matcher;
protocol = 'file';
}
} else if (PLUGIN_RE.test(matcher)) {
protocol = 'plugin';
} else if (PROXY_RE.test(matcher)) {
proxyName = RegExp.$1;
protocol = 'proxy';
} else {
var index = matcher.indexOf('://');
var origProto;
if (index !== -1) {
origProto = matcher.substring(0, index);
protocol = aliasProtocols[origProto];
}
if (!protocol) {
protocol = origProto;
if (matcher === 'host://') {
matcher = 'host://127.0.0.1';
}
} else if (protocol && (origProto === 'reqRules' || origProto === 'resRules')) {
isRules = true;
}
var isStatus = protocol === 'statusCode';
if (isStatus || protocol === 'redirect') {
isSpec = isStatus ? 1 : 2;
}
}
var rules = rulesMgr._rules;
var list =
protocol === 'sniCallback' ? rulesMgr._sniCallback : rules[protocol];
var useRealPort;
if (!list) {
protocol = 'rule';
list = LOCAL_RULE_RE.test(matcher) ? rules._localRule : rules.rule;
useRealPort = RegExp.$1;
} else if (!matcher.indexOf('G://clientCert://')) {
list = rules._clientCerts;
} else if (protocol == 'host') {
var protoIndex = matcher.indexOf(':') + 3;
var realProto = matcher.substring(0, protoIndex);
var opts = isHost(matcher.substring(protoIndex));
if (opts) {
matcher = realProto + opts.host;
port = opts.port;
}
}
var rule = {
not: not,
file: file,
isRules: isRules,
isSpec: isSpec,
name: protocol,
root: root,
wildcard: wildcard,
isRegExp: isRegExp,
isExact: isExact,
protocol: isRegExp ? null : util.getProtocol(pattern),
pattern: isRegExp ? pattern : util.formatUrl(pattern),
matcher: matcher,
port: port,
raw: raw,
isDomain:
!isRegExp &&
!not &&
(noSchema ? pattern : util.removeProtocol(rawPattern, true)).replace(/\?.*$/, '').indexOf(
'/'
) == -1,
rawPattern: rawPattern,
rawMatcher: rawMatcher,
filters: options.filters,
lineProps: options.lineProps,
hostFilter: options.hostFilter
};
if (options.rawProps.length) {
rule.rawProps = options.rawProps;
}
if (protocol === 'log' || protocol === 'weinre') {
rule.isTpl = false;
}
if (useRealPort) {
rule.realPort = config.realPort;
rule.matcher = rule.matcher.replace(
'realPort',
config.realPort || config.port
);
}
if (proxyName) {
switch (proxyName) {
case 'socks':
rule.isSocks = true;
break;
case 'https-proxy':
rule.isHttps = true;
break;
case 'internal-http-proxy':
case 'https2http-proxy':
case 'internal-proxy':
rule.isInternal = true;
break;
case 'internal-https-proxy':
rule.isInternal = true;
rule.isHttps = true;
break;
case 'http2https-proxy':
rule.isHttp2https = true;
break;
}
}
if (options.hasBodyFilter) {
rules._bodyFilters.push(rule);
}
if (util.isImportant(options)) {
for (var i = 0, len = list.length; i < len; i++) {
if (!util.isImportant(list[i])) {
return list.splice(i, 0, rule);
}
}
}
list.push(rule);
}
function isPattern(item) {
return (
PORT_PATTERN_RE.test(item) ||
isExactPattern(item) ||
isRegUrl(item, true) || // 缓存 regUrl
NO_SCHEMA_RE.test(item) ||
isNegativePattern(item) ||
WEB_PROTOCOL_RE.test(item) ||
util.isRegExp(item)
);
}
var IP_WITH_PORT_RE = /^\[([:\da-f.]+)\](?::(\d+))?$/i;
var IPV4_RE = /^(?:::(?:ffff:)?)?(\d+\.[\d.]+)(?:\:(\d+))?$/;
function parseHost(item) {
var port;
if (IP_WITH_PORT_RE.test(item)) {
item = RegExp.$1;
port = RegExp.$2;
}
if (IPV4_RE.test(item)) {
port = port || RegExp.$2;
item = RegExp.$1;
if (!net.isIP(item)) {
return false;
}
} else if (!net.isIP(item)) {
return false;
}
return {
host: item,
port: port
};
}
function isHost(item) {
var result = hostCache[item];
if (result == null) {
result = parseHost(item);
}
hostCache[item] = result;
return result;
}
function indexOfPattern(list) {
var ipIndex = -1;
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i];
if (isPattern(item)) {
return i;
}
if (!util.hasProtocol(item)) {
if (!isHost(item)) {
return i;
} else if (ipIndex === -1) {
ipIndex = i;
}
}
}
return ipIndex;
}
function resolveFilterPattern(matcher) {
var not, isInclude, filter, caseIns, wildcard;
if (PATTERN_FILTER_RE.test(matcher)) {
filter = RegExp.$1;
caseIns = RegExp.$2;
not = filter[0] === '!';
if (not) {
filter = filter.substring(1);
}
if (filter[0] === '/') {
filter = filter.substring(1);
}
return filter
? {
not: not,
filter: filter,
caseIns: caseIns
}
: false;
} else if (FILTER_RE.test(matcher)) {
filter = RegExp.$1;
if (!filter || filter === '!') {
return false;
}
isInclude = matcher[0] === 'i';
if (filter[0] === '!') {
not = !not;
filter = filter.substring(1);
}
if (util.isRegExp(filter)) {
filter = RegExp.$1;
caseIns = RegExp.$2;
return {
not: not,
isInclude: isInclude,
filter: filter,
caseIns: caseIns
};
}
if (filter[0] === '/' && filter[1] !== '/') {
wildcard = '/';
} else if (WILD_FILTER_RE.test(filter)) {
wildcard = RegExp.$1;
}
} else if (PATTERN_WILD_FILTER_RE.test(matcher)) {
not = RegExp.$1 || '';
wildcard = RegExp.$2;
} else {
return;
}
if (wildcard) {
matcher =
filter || matcher.substring(matcher.indexOf('://') + 3 + not.length);
var path = util.escapeRegExp(matcher.substring(wildcard.length));
if (path.indexOf('*') !== -1) {
path = path.replace(STAR_RE, pathToRegExp);
} else if (path && path[path.length - 1] !== '/') {
path += '(?:[/?]|$)';
}
return {
not: not,
isInclude: isInclude,
filter:
'^[a-z]+://' + (wildcard.length > 3 ? '[^?]' : '[^/?]') + '+/' + path
};
}
var result = isRegUrl('^' + filter);
if (result) {
result.not = not;
result.isInclude = isInclude;
return result;
}
}
function resolveMatchFilter(list) {
var matchers = [];
var lineProps = {};
var rawProps = [];
var filters, hasBodyFilter, hostFilter;
list.forEach(function (matcher) {
var rawMatcher = matcher;
if (INLINE_RE.test(matcher)) {
matcher = RegExp.$1 + RegExp.$2.slice(1, -1);
}
if (LINE_PROPS_RE.test(matcher)) {
rawProps.push(rawMatcher);
extend(lineProps, util.parseLineProps(matcher));
return;
}
var filter, not, isInclude, orgVal;
if (PROPS_FILTER_RE.test(matcher) || PURE_FILTER_RE.test(matcher)) {
var raw = RegExp['$&'];
var propName = RegExp.$1;
var value = RegExp.$2;
var isHostFilter = propName === 'host';
isInclude = matcher[1] === 'n';
if (value[0] === '!') {
not = !not;
value = value.substring(1);
}
var pattern;
var isIp = propName === 'i' || propName === 'ip';
var isClientPort, isServerPort, isClientIp, isServerIp;
if (!isIp) {
isClientPort = propName === 'clientPort';
if (!isClientPort) {
isServerPort = propName === 'serverPort';
if (!isServerPort) {
isClientIp = (propName === 'clientIp' || propName === 'clientIP');
isServerIp =
!isClientIp &&
(propName === 'serverIp' || propName === 'serverIP');
}
}
}
if (isClientPort || isServerPort) {
pattern = util.toRegExp(value);
if (isClientPort) {
propName = pattern ? 'cpPattern' : 'clientPort';
} else {
propName = pattern ? 'spPattern' : 'serverPort';
}
value = pattern || value.toLowerCase();
} else if (isIp || isClientIp || isServerIp) {
pattern = util.toRegExp(value);
if (!pattern && !net.isIP(value)) {
return;
}
if (isIp) {
propName = pattern ? 'iPattern' : 'ip';
} else if (isClientIp) {
propName = pattern ? 'clientPattern' : 'clientIp';
} else if (isServerIp) {
propName = pattern ? 'serverPattern' : 'serverIp';
}
value = pattern || value;
} else if (propName[0] === 'm') {
pattern = util.toRegExp(value, true);
propName = pattern ? 'mPattern' : 'method';
value = pattern || value.toUpperCase();
} else if (propName === 'from') {
pattern = null;
propName = 'from';
value = value.toLowerCase();
} else if (propName === 's' || propName === 'statusCode') {
pattern = util.toRegExp(value);
propName = pattern ? 'sPattern' : 'statusCode';
value = pattern || value.toLowerCase();
} else if (propName === 'b' || propName === 'body') {
hasBodyFilter = true;
pattern = util.toRegExp(value);
if (pattern) {
propName = 'bodyPattern';
value = pattern;
} else {
propName = 'body';
value = {
orgVal: util.encodeURIComponent(value).toLowerCase(),
value: value.toLowerCase()
};
}
} else if (propName === 'remoteAddress') {
pattern = util.toRegExp(value);
if (pattern) {
propName = 'addrPattern';
}
value = pattern || value.toLowerCase();
} else if (propName === 'remotePort') {
pattern = util.toRegExp(value);
if (pattern) {
propName = 'portPattern';
}
value = pattern || value.toLowerCase();
} else if (isHostFilter) {
pattern = util.toRegExp(value);
if (pattern) {
propName = 'hostPattern';
}
value = pattern || value.toLowerCase();
} else {
var isHeader = propName !== 'env' && propName !== 'chance' && propName !== 'probability';
var index = value.indexOf('=');
if (isHeader && index === -1) {
index = value.indexOf(':');
}
var key = index === -1 ? value : value.substring(0, index);
if (isHeader) {
key = key.toLowerCase();
}
var lastIndex = key.length - 1;
if (key[lastIndex] === '!') {
key = key.substring(0, lastIndex);
if (!key) {
return;
}
not = !not;
}
orgVal = index === -1 ? '' : value.substring(index + 1);
value = { key: key };
if ((pattern = util.toRegExp(orgVal))) {
value.hPattern = pattern;
} else {
value.orgVal = orgVal = orgVal.toLowerCase();
value.value = util.encodeURIComponent(orgVal);
}
if (isHeader) {
switch (propName[2]) {
case 'q':
propName = 'reqHeader';
break;
case 's':
propName = 'resHeader';
break;
case 'v':
propName = 'env';
break;
default:
propName = 'header';
}
}
}
filter = { not: not, isInclude: isInclude };
filter[propName] = value;
filter.raw = raw;
if (isHostFilter) {
hostFilter = hostFilter || [];
hostFilter.push(filter);
} else {
filters = filters || [];
filters.push(filter);
}
return;
}
var result = resolveFilterPattern(matcher);
if (result === false) {
return;
} else if (!result) {
matchers.push(rawMatcher);
return;
}
if (result.pattern) {
filters = filters || [];
result.raw = rawMatcher;
return filters.push(result);
}
filter = '/' + result.filter + '/' + (result.caseIns ? 'i' : '');
if ((filter = util.toRegExp(filter))) {
filters = filters || [];
filters.push({
raw: rawMatcher,
pattern: filter,
not: result.not,
isInclude: result.isInclude
});
}
});
return {
rawProps: rawProps,
hasBodyFilter: hasBodyFilter,
matchers: matchers,
hostFilter: hostFilter,
filters: filters,
lineProps: lineProps
};
}
function getPluginName(root) {
return root && ROOT_PLUGIN_RE.test(root) ? util.getPluginFile(RegExp.$1) : undefined;
}
function parseText(rulesMgr, text, root, file) {
var pluginVars = rulesMgr._globalPluginVars;
var enabledList = rulesMgr._enabledList || [];
rulesMgr._enabledList = enabledList;
getLines(text, root).forEach(function (line) {
var raw = line;
var rawLine = line;
line = removeComment(line);
line = line && line.split(/\s+/);
var len = line && line.length;
if (len === 1 && PLUGIN_VAR_RE.test(line[0])) {
var name = RegExp.$1;
var value = RegExp.$3;
if (value) {
var vars = pluginVars[name];
if (!vars) {
vars = [value];
pluginVars[name] = vars;
} else if (vars.indexOf(value) === -1) {
vars.push(value);
}
}
enabledList.push([rawLine, file]);
return;
}
if (!len || len < 2) {
return;
}
line = line.map(formatShorthand);
var patternIndex = indexOfPattern(line);
if (patternIndex === -1) {
return;
}
var pattern = line[0];
var result = resolveMatchFilter(line.slice(1));
var matchers = result.matchers;
if (patternIndex > 0) {
//supports: operator-uri1 operator-uriX pattern1 pattern2 ... patternN
var opList = [pattern