mirror of
https://github.com/avwo/whistle.git
synced 2026-10-19 07:01:44 +08:00
727 lines
20 KiB
JavaScript
727 lines
20 KiB
JavaScript
var Pac = require('node-pac');
|
|
var net = require('net');
|
|
var LRU = require('lru-cache');
|
|
var path = require('path');
|
|
var parseUrl = require('../util/parse-url-safe');
|
|
var extend = require('extend');
|
|
var parseQuery = require('querystring').parse;
|
|
var lookup = require('./dns');
|
|
var Rules = require('./rules');
|
|
var values = require('./util').values;
|
|
var util = require('../util');
|
|
var logger = require('../util/logger');
|
|
var fileMgr = require('../util/file-mgr');
|
|
var config = require('../config');
|
|
|
|
var tplCache = new LRU({ max: 36 });
|
|
var rulesMgrCache = new LRU({ max: 16 });
|
|
var clientCerts = new LRU({ max: 60, maxAge: 1000 * 60 * 60 });
|
|
var rules = new Rules();
|
|
var tempRules = new Rules();
|
|
var cachedPacs = {};
|
|
var RULES_HEADER = 'x-whistle-rule-value';
|
|
var KV_HEADER = 'x-whistle-key-value';
|
|
var KEY_HEADER = 'x-whistle-rule-key';
|
|
var HOST_HEADER = 'x-whistle-rule-host';
|
|
var LOCALHOST = '127.0.0.1';
|
|
var resolveReqRules = rules.resolveReqRules.bind(rules);
|
|
var AUTH_RE = /^([^\\/]+)@/;
|
|
var PLUGIN_VAR_RE = /^[a-z\d_\-]+\./;
|
|
|
|
rules._isGlobal = true;
|
|
|
|
exports.Rules = Rules;
|
|
if (config.networkMode && !config.shadowRulesMode) {
|
|
exports.parse = util.noop;
|
|
} else {
|
|
exports.parse = function (text, root, _values) {
|
|
if (config.pluginsMode || config.shadowRulesMode) {
|
|
text = config.shadowRules || '';
|
|
}
|
|
if (config.shadowValues) {
|
|
_values = _values
|
|
? extend({}, config.shadowValues, _values)
|
|
: config.shadowValues;
|
|
}
|
|
rules.parse(text, root, _values);
|
|
};
|
|
}
|
|
exports.append = rules.append.bind(rules);
|
|
exports.resolveSNICallback = rules.resolveSNICallback.bind(rules);
|
|
exports.resolveHost = rules.resolveHost.bind(rules);
|
|
exports.resolveInternalHost = rules.resolveInternalHost.bind(rules);
|
|
exports.resolveProxy = rules.resolveProxy.bind(rules);
|
|
exports.resolveEnable = rules.resolveEnable.bind(rules);
|
|
exports.hasReqScript = rules.hasReqScript.bind(rules);
|
|
exports.resolveDisable = rules.resolveDisable.bind(rules);
|
|
exports.resolvePipe = rules.resolvePipe.bind(rules);
|
|
exports.resolveRule = rules.resolveRule.bind(rules);
|
|
exports.resolveRules = resolveReqRules;
|
|
exports.resolveResRules = rules.resolveResRules.bind(rules);
|
|
exports.resolveBodyFilter = rules.resolveBodyFilter.bind(rules);
|
|
exports.lookupHost = rules.lookupHost.bind(rules);
|
|
exports.resolveLocalRule = rules.resolveLocalRule.bind(rules);
|
|
exports.clearAppend = rules.clearAppend.bind(rules);
|
|
exports.resolveTplVar = Rules.resolveTplVar;
|
|
|
|
exports.disableDnsCache = function () {
|
|
Rules.disableDnsCache();
|
|
};
|
|
|
|
var dnsResolve = function (host, callback) {
|
|
return lookup(host, callback || util.noop, true);
|
|
};
|
|
var PROXY_HOSTS_RE = /[?&]proxyHosts?(?:&|$)/i;
|
|
var P_HOST_RE = /[?&]host=([\w.:-]+)(?:&|$)/i;
|
|
|
|
function isProxyHost(req, proxy, host) {
|
|
if (proxy) {
|
|
req._proxyTunnel =
|
|
isLineProp(proxy, 'proxyTunnel') ||
|
|
isLineProp(host, 'proxyTunnel') ||
|
|
util.isEnable(req, 'proxyTunnel') ||
|
|
isProxyEnable(req, 'proxyTunnel');
|
|
if (isLineProp(proxy, 'proxyHost')) {
|
|
return true;
|
|
}
|
|
}
|
|
return isLineProp(host, 'proxyHost');
|
|
}
|
|
|
|
function isProxyEnable(req, name) {
|
|
var props = req._proxyProps;
|
|
if (props === undefined) {
|
|
props = rules.resolveProxyProps(req);
|
|
req._proxyProps = props || null;
|
|
}
|
|
return props && util.isEnable(props, name);
|
|
}
|
|
|
|
function isLineProp(rule, name) {
|
|
return rule && rule.lineProps[name];
|
|
}
|
|
|
|
function resolveRulesFromList(list, fnName, req, options) {
|
|
var rule;
|
|
for (var i = 0, len = list.length; i < len; i++) {
|
|
var mgr = list[i];
|
|
var _rule = mgr && mgr[fnName](req, options);
|
|
if (_rule) {
|
|
if (util.isImportant(_rule)) {
|
|
return _rule;
|
|
}
|
|
rule = rule || _rule;
|
|
}
|
|
}
|
|
return rule;
|
|
}
|
|
|
|
exports.getProxy = function (url, req, callback) {
|
|
if (!req) {
|
|
return callback();
|
|
}
|
|
var reqRules = req.rules;
|
|
req.curUrl = url;
|
|
if (!reqRules) {
|
|
return rules.lookupHost(req, callback);
|
|
}
|
|
|
|
delete reqRules.proxy;
|
|
delete reqRules.pac;
|
|
var pRules = req.pluginRules;
|
|
var fRules = req.rulesFileMgr;
|
|
if (fRules) {
|
|
fRules._values = req._scriptValues;
|
|
}
|
|
var hRules = req.headerRulesMgr;
|
|
var filter = extend(
|
|
rules.resolveFilter(req),
|
|
pRules && pRules.resolveFilter(req),
|
|
fRules && fRules.resolveFilter(req),
|
|
hRules && hRules.resolveFilter(req)
|
|
);
|
|
var ignoreProxy;
|
|
var proxy;
|
|
var pacRule;
|
|
var host = rules.getHost(req, pRules, fRules, hRules);
|
|
var hostValue = util.getMatcherValue(host) || '';
|
|
if (config.multiEnv || req._headerRulesFirst) {
|
|
proxy = resolveRulesFromList([pRules, hRules, rules, fRules], 'resolveProxy', req, hostValue);
|
|
} else {
|
|
proxy = resolveRulesFromList([pRules, rules, fRules, hRules], 'resolveProxy', req, hostValue);
|
|
}
|
|
var proxyHostOnly = isLineProp(proxy, 'proxyHostOnly');
|
|
var proxyHost =
|
|
proxyHostOnly ||
|
|
util.isEnable(req, 'proxyHost') ||
|
|
isProxyEnable(req, 'proxyHost');
|
|
if (proxy) {
|
|
var protocol = proxy.matcher.substring(0, proxy.matcher.indexOf(':'));
|
|
ignoreProxy =
|
|
!filter['ignore:' + protocol] &&
|
|
(util.isIgnored(filter, 'proxy') || util.isIgnored(filter, protocol));
|
|
if (ignoreProxy) {
|
|
proxy = null;
|
|
} else {
|
|
proxyHost = proxyHost || PROXY_HOSTS_RE.test(proxy.matcher);
|
|
}
|
|
}
|
|
|
|
if (!ignoreProxy) {
|
|
proxyHost = isProxyHost(req, proxy, host) || proxyHost; // 不能调换顺序
|
|
}
|
|
var setHost = function () {
|
|
if (!host || req._isProxyReq) {
|
|
return false;
|
|
}
|
|
reqRules.host = host;
|
|
var hostname = util.removeProtocol(host.matcher, true);
|
|
if (!net.isIP(hostname)) {
|
|
req.curUrl = hostname || url;
|
|
rules.lookupHost(req, function (err, ip) {
|
|
callback(err, ip, host.port, host);
|
|
});
|
|
} else {
|
|
callback(null, hostname, host.port, host);
|
|
}
|
|
return true;
|
|
};
|
|
var resolvePacRule = function () {
|
|
if (pacRule != null) {
|
|
return;
|
|
}
|
|
if (util.isIgnored(filter, 'pac')) {
|
|
pacRule = false;
|
|
return;
|
|
}
|
|
if (config.multiEnv || req._headerRulesFirst) {
|
|
pacRule =
|
|
(pRules && pRules.resolvePac(req)) ||
|
|
(hRules && hRules.resolvePac(req)) ||
|
|
rules.resolvePac(req) ||
|
|
(fRules && fRules.resolvePac(req)) ||
|
|
false;
|
|
} else {
|
|
pacRule =
|
|
(pRules && pRules.resolvePac(req)) ||
|
|
rules.resolvePac(req) ||
|
|
(fRules && fRules.resolvePac(req)) ||
|
|
(hRules && hRules.resolvePac(req)) ||
|
|
false;
|
|
}
|
|
if (pacRule) {
|
|
proxyHostOnly = isLineProp(pacRule, 'proxyHostOnly');
|
|
proxyHost =
|
|
proxyHost || proxyHostOnly || isLineProp(pacRule, 'proxyHost');
|
|
}
|
|
};
|
|
if (host) {
|
|
if (!proxyHost && !ignoreProxy) {
|
|
resolvePacRule();
|
|
}
|
|
if (proxyHost) {
|
|
req._phost = parseUrl(util.setProtocol(host.matcher + (host.port ? ':' + host.port : '')));
|
|
} else if (
|
|
!isLineProp(proxy, 'proxyFirst') &&
|
|
!isLineProp(host, 'proxyFirst') &&
|
|
!util.isEnable(req, 'proxyFirst') &&
|
|
!isProxyEnable(req, 'proxyFirst')
|
|
) {
|
|
return setHost();
|
|
}
|
|
proxyHost = true;
|
|
reqRules.host = host;
|
|
req._enableProxyHost = true;
|
|
}
|
|
if (ignoreProxy || (proxyHostOnly && !req._phost)) {
|
|
req.curUrl = url;
|
|
return setHost() || rules.lookupHost(req, callback);
|
|
}
|
|
if (proxy) {
|
|
if (!req._phost && P_HOST_RE.test(proxy.matcher)) {
|
|
req._phost = parseUrl(util.setProtocol(RegExp.$1));
|
|
}
|
|
reqRules.proxy = proxy;
|
|
return callback();
|
|
}
|
|
resolvePacRule();
|
|
var pacUrl = util.getMatcherValue(pacRule);
|
|
if (!pacUrl) {
|
|
return setHost() || callback();
|
|
}
|
|
if (AUTH_RE.test(pacUrl)) {
|
|
var auth = RegExp.$1;
|
|
req._pacAuth = auth;
|
|
pacUrl = pacUrl.substring(auth.length + 1);
|
|
}
|
|
pacUrl = /^https?\:\/\//.test(pacUrl)
|
|
? pacUrl
|
|
: util.join(pacRule.root, pacUrl);
|
|
var pac = cachedPacs[pacUrl];
|
|
if (pac) {
|
|
delete cachedPacs[pacUrl];
|
|
cachedPacs[pacUrl] = pac;
|
|
} else {
|
|
var list = Object.keys(cachedPacs);
|
|
if (list.length >= 10) {
|
|
delete cachedPacs[list[0]];
|
|
}
|
|
cachedPacs[pacUrl] = pac = new Pac(pacUrl, dnsResolve);
|
|
}
|
|
reqRules.pac = pacRule;
|
|
return pac.findWhistleProxyForURL(
|
|
url.replace('tunnel:', 'https:'),
|
|
function (err, rule) {
|
|
if (rule) {
|
|
tempRules.parse(pacRule.rawPattern + ' ' + rule);
|
|
req.curUrl = url;
|
|
if ((proxy = tempRules.resolveProxy(req, hostValue))) {
|
|
proxyHost = isProxyHost(req, proxy) || proxyHost; // 不能调换顺序
|
|
req._proxyTunnel =
|
|
req._proxyTunnel || isLineProp(pacRule, 'proxyTunnel');
|
|
var protocol = proxy.matcher.substring(0, proxy.matcher.indexOf(':'));
|
|
if (!util.isIgnored(filter, protocol)) {
|
|
reqRules.proxy = proxy;
|
|
reqRules.proxy.raw = pacRule.raw;
|
|
}
|
|
}
|
|
}
|
|
if (reqRules.proxy) {
|
|
(!proxyHost && setHost()) || callback();
|
|
} else {
|
|
req.curUrl = url;
|
|
setHost() || rules.lookupHost(req, callback);
|
|
}
|
|
logger.error(err);
|
|
}
|
|
);
|
|
};
|
|
|
|
function tpl(str, data) {
|
|
if (
|
|
typeof str !== 'string' ||
|
|
str.indexOf('<%') === -1 ||
|
|
str.indexOf('%>') === -1
|
|
) {
|
|
return str + '';
|
|
}
|
|
var key = str;
|
|
var fn = tplCache.get(key);
|
|
if (!fn) {
|
|
str = str
|
|
.replace(/[\u2028\u2029]/g, '')
|
|
.replace(/\t/g, ' ')
|
|
.replace(/\r?\n|\r/g, '\t')
|
|
.split('<%')
|
|
.join('\u2028')
|
|
.replace(/((^|%>)[^\u2028]*)'/g, '$1\r')
|
|
.replace(/\u2028=(.*?)%>/g, '\',$1,\'')
|
|
.split('\u2028')
|
|
.join('\');')
|
|
.split('%>')
|
|
.join('p.push(\'')
|
|
.split('\r')
|
|
.join('\\\'');
|
|
try {
|
|
fn = new Function(
|
|
'obj',
|
|
'var p=[],print=function(){p.push.apply(p,arguments);};' +
|
|
'with(obj){p.push(\'' +
|
|
str +
|
|
'\');}return p.join(\'\');'
|
|
);
|
|
} catch (e) {
|
|
fn = e;
|
|
throw e;
|
|
} finally {
|
|
tplCache.set(key, fn);
|
|
}
|
|
} else if (typeof fn !== 'function') {
|
|
throw fn;
|
|
}
|
|
return fn(data || {}).replace(/\t/g, '\n');
|
|
}
|
|
|
|
function getScriptContext(req, res, body, pattern) {
|
|
var ip = req.clientIp || LOCALHOST;
|
|
var ctx = req.scriptContenxt;
|
|
if (!ctx) {
|
|
var headers = extend(true, {}, req.headers);
|
|
ctx = req.scriptContenxt = {
|
|
pattern: pattern,
|
|
version: config.version,
|
|
port: config.port,
|
|
uiHost: 'local.wproxy.org',
|
|
uiPort: config.uiport,
|
|
url: req.fullUrl,
|
|
method: util.toUpperCase(req.method) || 'GET',
|
|
httpVersion: req.httpVersion || '1.1',
|
|
isLocalAddress: function (_ip) {
|
|
return util.isLocalAddress(_ip || ip);
|
|
},
|
|
ip: ip,
|
|
clientIp: ip,
|
|
clientPort: req.clientPort,
|
|
headers: headers,
|
|
reqHeaders: headers,
|
|
body: body || '',
|
|
reqScriptData: {},
|
|
res: null
|
|
};
|
|
}
|
|
ctx.rules = [];
|
|
ctx.values = {};
|
|
ctx.value = req.globalValue;
|
|
ctx.getValue = function (key, onlyValues) {
|
|
var value = !onlyValues && req._inlineValues && req._inlineValues[key];
|
|
return typeof value === 'string' ? value : values.get(key);
|
|
};
|
|
ctx.parseUrl = parseUrl;
|
|
ctx.parseQuery = parseQuery;
|
|
ctx.tpl = ctx.render = tpl;
|
|
if (res) {
|
|
ctx.statusCode = res.statusCode;
|
|
ctx.serverIp = req.hostIp || LOCALHOST;
|
|
ctx.resHeaders = extend(true, {}, res.headers);
|
|
} else {
|
|
ctx.statusCode = '';
|
|
ctx.serverIp = '';
|
|
ctx.resHeaders = '';
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
function getReqPayload(req, res, cb) {
|
|
if (res) {
|
|
return cb();
|
|
}
|
|
if (req.getPayload && util.hasRequestBody(req)) {
|
|
if (typeof req._reqBody === 'string') {
|
|
cb(req._reqBody);
|
|
} else {
|
|
req.getPayload(function (_, payload) {
|
|
cb(fileMgr.decode(payload));
|
|
});
|
|
}
|
|
} else {
|
|
cb();
|
|
}
|
|
}
|
|
|
|
function execRulesScript(script, req, res, body, pattern) {
|
|
var context = getScriptContext(req, res, body, pattern);
|
|
if (util.execScriptSync(script, context) && Array.isArray(context.rules)) {
|
|
return {
|
|
rules: context.rules.join('\n').trim(),
|
|
values: context.values
|
|
};
|
|
}
|
|
return '';
|
|
}
|
|
exports.execRulesScript = execRulesScript;
|
|
|
|
function handleDynamicRules(script, req, res, cb) {
|
|
util.getRuleValue(script, function (list) {
|
|
var scriptItem, index, text;
|
|
if (list) {
|
|
index = script.scriptIndex;
|
|
scriptItem = script.list[index];
|
|
text = scriptItem && list[index];
|
|
}
|
|
if (!scriptItem || util.isRulesContent(text)) {
|
|
return cb(list && list.join('\n'));
|
|
}
|
|
getReqPayload(req, res, function (body) {
|
|
var result = execRulesScript(text, req, res, body, script.rawPattern);
|
|
list[index] = result.rules;
|
|
cb(list.join('\n'), result.values);
|
|
});
|
|
}, null, null, null, req);
|
|
}
|
|
|
|
function resolveRulesFile(req, callback) {
|
|
if (req.rules.G) {
|
|
var varMap = {};
|
|
var globalValue;
|
|
var pList;
|
|
req.rules.G.list.forEach(function (item) {
|
|
if (item.matcher[0] !== 'P') {
|
|
if (!globalValue) {
|
|
globalValue = item;
|
|
}
|
|
return;
|
|
}
|
|
var value = util.getMatcherValue(item);
|
|
var index = value.indexOf(PLUGIN_VAR_RE.test(value) ? '.' : '=');
|
|
if (index !== -1) {
|
|
var name = value.substring(0, index);
|
|
var plugin = exports.getPlugin(name);
|
|
if (!plugin || !plugin.pluginVars) {
|
|
return;
|
|
}
|
|
value = value.substring(index + 1);
|
|
if (value) {
|
|
pList = pList || [];
|
|
pList.push(item);
|
|
var list = varMap[name] || [];
|
|
varMap[name] = list;
|
|
if (list.indexOf(value) === -1) {
|
|
list.push(value);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
req._pluginVars = varMap;
|
|
req.rules.P = pList;
|
|
req.rules.G = globalValue;
|
|
req.globalValue = util.getMatcherValue(globalValue);
|
|
}
|
|
handleDynamicRules(req.rules.rulesFile, req, null, function (text, vals) {
|
|
if (text) {
|
|
var rulesFileMgr = rulesMgrCache.get(text);
|
|
if (!rulesFileMgr) {
|
|
rulesFileMgr = new Rules(vals);
|
|
rulesFileMgr.parse(text);
|
|
rulesMgrCache.set(text, rulesFileMgr);
|
|
}
|
|
rulesFileMgr._values = vals;
|
|
req._scriptValues = vals;
|
|
req.rulesFileMgr = rulesFileMgr;
|
|
req.curUrl = req.fullUrl;
|
|
text = req.rulesFileMgr.resolveReqRules(req);
|
|
}
|
|
// 不能放到if里面
|
|
util.mergeRules(req, text);
|
|
callback();
|
|
});
|
|
}
|
|
|
|
exports.resolveRulesFile = resolveRulesFile;
|
|
exports.resolveResRulesFile = function (req, res, callback) {
|
|
handleDynamicRules(
|
|
req.rules && req.rules.resScript,
|
|
req,
|
|
res,
|
|
function (text, vals) {
|
|
text = text && text.trim();
|
|
callback(
|
|
text && {
|
|
text: text,
|
|
values: vals
|
|
}
|
|
);
|
|
}
|
|
);
|
|
};
|
|
|
|
function getValue(req, key, keep) {
|
|
var value = req.headers[key];
|
|
if (value) {
|
|
if (!keep && config.strict) {
|
|
value = null;
|
|
} else {
|
|
req.rulesHeaders[key] = value;
|
|
}
|
|
delete req.headers[key];
|
|
}
|
|
try {
|
|
return value && decodeURIComponent(value);
|
|
} catch (e) {}
|
|
return value;
|
|
}
|
|
|
|
function initHeaderRules(req, needBodyFilters) {
|
|
if (req._bodyFilters !== undefined) {
|
|
return;
|
|
}
|
|
req._bodyFilters = null;
|
|
req.rulesHeaders = {};
|
|
var isPluginReq = req.isPluginReq && !req._isProxyReq;
|
|
req._headerRulesFirst = isPluginReq;
|
|
var rulesHeader = getValue(req, RULES_HEADER, isPluginReq);
|
|
var hostHeader = util.trimStr(getValue(req, HOST_HEADER));
|
|
var keyHeader = util.trimStr(getValue(req, KEY_HEADER));
|
|
var kvHeader = getValue(req, KV_HEADER, isPluginReq);
|
|
|
|
var ruleValue = util.trimStr(rulesHeader);
|
|
if (hostHeader) {
|
|
ruleValue = ruleValue + '\n' + hostHeader;
|
|
}
|
|
if (keyHeader) {
|
|
keyHeader = util.trimStr(values.get(keyHeader));
|
|
if (keyHeader) {
|
|
ruleValue = keyHeader + '\n' + ruleValue;
|
|
}
|
|
}
|
|
var curVars = rules._globalPluginVars;
|
|
var value;
|
|
req._globalPluginVars = {};
|
|
if (ruleValue) {
|
|
var rulesMgr = new Rules(util.parseJSON(kvHeader));
|
|
rulesMgr.parse(ruleValue);
|
|
req.headerRulesMgr = rulesMgr;
|
|
var bodyFilters = needBodyFilters && rulesMgr._rules._bodyFilters;
|
|
if (bodyFilters && bodyFilters.length) {
|
|
req._bodyFilters = rules._rules._bodyFilters.concat(bodyFilters);
|
|
}
|
|
var headerVars = rulesMgr._globalPluginVars;
|
|
var vars = extend({}, curVars, headerVars);
|
|
Object.keys(vars).forEach(function (key) {
|
|
var plugin = exports.getPlugin(key);
|
|
if (!plugin || !plugin.pluginVars) {
|
|
return;
|
|
}
|
|
var headerVal = headerVars[key];
|
|
var curVal = curVars[key];
|
|
if (curVal && headerVal) {
|
|
vars[key] = headerVal.concat(curVal);
|
|
}
|
|
value = vars[key];
|
|
req._globalPluginVars[key] = value;
|
|
});
|
|
} else {
|
|
Object.keys(curVars).forEach(function (key) {
|
|
if (!exports.getPlugin || exports.getPlugin(key)) {
|
|
value = curVars[key];
|
|
req._globalPluginVars[key] = value;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.initHeaderRules = initHeaderRules;
|
|
|
|
function initRules(req) {
|
|
var fullUrl = req.fullUrl || util.getFullUrl(req);
|
|
req.curUrl = fullUrl;
|
|
initHeaderRules(req);
|
|
if (req.headerRulesMgr) {
|
|
if (config.multiEnv || req._headerRulesFirst) {
|
|
req.rules = resolveReqRules(req);
|
|
util.mergeRules(req, req.headerRulesMgr.resolveReqRules(req));
|
|
} else {
|
|
req.rules = req.headerRulesMgr.resolveReqRules(req);
|
|
util.mergeRules(req, resolveReqRules(req));
|
|
}
|
|
} else {
|
|
req.rules = resolveReqRules(req);
|
|
}
|
|
return req.rules;
|
|
}
|
|
|
|
exports.initRules = initRules;
|
|
|
|
var HTTPS_RE = /^(?:ws|http)s:\/\//;
|
|
|
|
function checkCache(cacheKey, callback) {
|
|
var result = clientCerts.peek(cacheKey);
|
|
if (result) {
|
|
if (result.pending) {
|
|
result.push(callback);
|
|
} else {
|
|
callback(result);
|
|
}
|
|
return true;
|
|
} else {
|
|
result = [callback];
|
|
result.pending = true;
|
|
clientCerts.set(cacheKey, result);
|
|
}
|
|
}
|
|
|
|
exports.getClientCert = function (req, cb) {
|
|
if (!req) {
|
|
return cb();
|
|
}
|
|
req.curUrl = req.realUrl || req.fullUrl;
|
|
if (!HTTPS_RE.test(req.curUrl)) {
|
|
return cb();
|
|
}
|
|
var pRules = req.pluginRules;
|
|
var fRules = req.rulesFileMgr;
|
|
var hRules = req.headerRulesMgr;
|
|
var rule;
|
|
if (config.multiEnv || req._headerRulesFirst) {
|
|
rule = resolveRulesFromList([pRules, hRules, rules, fRules], 'resolveClientCert', req);
|
|
} else {
|
|
rule = resolveRulesFromList([pRules, rules, fRules, hRules], 'resolveClientCert', req);
|
|
}
|
|
if (!rule) {
|
|
return cb();
|
|
}
|
|
req.rules.clientCert = rule;
|
|
var matcher = rule.matcher.substring(17);
|
|
matcher = matcher && util.parseJSON(matcher);
|
|
if (!matcher) {
|
|
return cb();
|
|
}
|
|
var base = util.getString(matcher.base);
|
|
var key = util.getString(matcher.key);
|
|
var cert = util.getString(matcher.cert);
|
|
var cacheKey;
|
|
try {
|
|
if (key && cert) {
|
|
if (base) {
|
|
key = path.join(base, key);
|
|
cert = path.join(base, cert);
|
|
}
|
|
cacheKey = 'cert\n' + key + '\n' + cert;
|
|
if (
|
|
checkCache(cacheKey, function (data) {
|
|
if (data) {
|
|
cb(data[0], data[1], false, cacheKey);
|
|
} else {
|
|
cb();
|
|
}
|
|
})
|
|
) {
|
|
return;
|
|
}
|
|
return fileMgr.readFileList([key, cert], function (data) {
|
|
var list = clientCerts.peek(cacheKey);
|
|
if (data[0] && data[1] && data[0].length && data[1].length) {
|
|
clientCerts.set(cacheKey, data);
|
|
} else {
|
|
clientCerts.del(cacheKey);
|
|
data = '';
|
|
}
|
|
list.forEach(function (fn) {
|
|
fn(data);
|
|
});
|
|
});
|
|
}
|
|
var pwd = util.getString(matcher.pwd || matcher.passphrase);
|
|
var pfx = util.getString(matcher.pfx);
|
|
if (pfx) {
|
|
if (base) {
|
|
pfx = path.join(base, key);
|
|
}
|
|
cacheKey = 'pfx\n' + pwd + '\n' + pfx;
|
|
if (
|
|
checkCache(cacheKey, function (buf) {
|
|
if (buf) {
|
|
cb(pwd, buf, true, cacheKey);
|
|
} else {
|
|
cb();
|
|
}
|
|
})
|
|
) {
|
|
return;
|
|
}
|
|
return fileMgr.readFile(pfx, function (buf) {
|
|
var list = clientCerts.peek(cacheKey);
|
|
if (buf && buf.length) {
|
|
clientCerts.set(cacheKey, buf);
|
|
} else {
|
|
clientCerts.del(cacheKey);
|
|
buf = '';
|
|
}
|
|
list.forEach(function (fn) {
|
|
fn(buf);
|
|
});
|
|
});
|
|
}
|
|
} catch (e) {}
|
|
cb();
|
|
};
|