whistle 2.9.109 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/js/log.js +8 -4
- package/bin/util.js +1 -1
- package/biz/index.js +12 -10
- package/biz/webui/cgi-bin/certs/active.js +6 -0
- package/biz/webui/cgi-bin/certs/remove.js +0 -1
- package/biz/webui/cgi-bin/certs/upload.js +0 -1
- package/biz/webui/cgi-bin/util.js +7 -20
- package/biz/webui/htdocs/index.html +1 -1
- package/biz/webui/htdocs/js/index.js +43 -43
- package/biz/webui/lib/index.js +10 -13
- package/lib/config.js +17 -36
- package/lib/handlers/file-proxy.js +2 -1
- package/lib/handlers/http-proxy.js +1 -1
- package/lib/https/ca.js +34 -27
- package/lib/https/h2.js +1 -1
- package/lib/https/index.js +21 -17
- package/lib/index.js +1 -1
- package/lib/init.js +13 -12
- package/lib/inspectors/req.js +13 -10
- package/lib/inspectors/res.js +375 -390
- package/lib/inspectors/rules.js +11 -0
- package/lib/plugins/compat.js +108 -0
- package/lib/plugins/get-plugins-sync.js +1 -4
- package/lib/plugins/index.js +92 -284
- package/lib/plugins/load-plugin.js +205 -223
- package/lib/plugins/module-paths.js +3 -4
- package/lib/plugins/proxy.js +4 -7
- package/lib/rules/index.js +69 -66
- package/lib/rules/protocols.js +6 -0
- package/lib/rules/rules.js +44 -32
- package/lib/rules/util.js +30 -0
- package/lib/service/composer.js +5 -5
- package/lib/service/util.js +7 -21
- package/lib/tunnel.js +15 -12
- package/lib/upgrade.js +9 -10
- package/lib/util/common.js +171 -48
- package/lib/util/file-mgr.js +3 -7
- package/lib/util/index.js +101 -143
- package/package.json +1 -1
- package/README-en_US.md +0 -80
- package/biz/webui/htdocs/img/assistant.svg +0 -1
package/biz/webui/lib/index.js
CHANGED
|
@@ -40,6 +40,7 @@ var DONT_CHECK_PATHS = ['/cgi-bin/server-info', '/cgi-bin/plugins/is-enable', '/
|
|
|
40
40
|
'/preview.html', '/cgi-bin/rootca', '/cgi-bin/check-update', '/cgi-bin/log/set', '/cgi-bin/status'];
|
|
41
41
|
var GUEST_PATHS = ['/cgi-bin/composer', '/cgi-bin/socket/data', '/cgi-bin/abort', '/cgi-bin/socket/abort',
|
|
42
42
|
'/cgi-bin/socket/change-status', '/cgi-bin/sessions/export'];
|
|
43
|
+
var CORS_PATHS = ['/cgi-bin/status', '/cgi-bin/rootca'];
|
|
43
44
|
var PLUGIN_PATH_RE = /^\/(whistle|plugin)\.([^/?#]+)(\/)?/;
|
|
44
45
|
var STATIC_SRC_RE = /\.(?:ico|js|css|png)$/i;
|
|
45
46
|
var UPLOAD_URLS = ['/cgi-bin/values/upload', '/cgi-bin/composer', '/cgi-bin/download'];
|
|
@@ -52,7 +53,6 @@ var INSPECTOR_HTML = fs.readFileSync(path.join(__dirname, '../../../assets/tab.h
|
|
|
52
53
|
var MODAL_HTML = fs.readFileSync(path.join(__dirname, '../../../assets/modal.html'));
|
|
53
54
|
var MENU_URL = '???_WHISTLE_PLUGIN_EXT_CONTEXT_MENU_' + config.port + '???';
|
|
54
55
|
var INSPECTOR_URL = '???_WHISTLE_PLUGIN_INSPECTOR_TAB_' + config.port + '???';
|
|
55
|
-
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
56
56
|
var KEY_RE_G = /\${[^{}\s]+}|{\S+}/g;
|
|
57
57
|
var COMMENT_RE = /#[^\r\n]*$/mg;
|
|
58
58
|
|
|
@@ -148,7 +148,7 @@ function verifyLogin(req, res, auth) {
|
|
|
148
148
|
if (correctKey === lkey) {
|
|
149
149
|
return true;
|
|
150
150
|
}
|
|
151
|
-
var headerAuth = parseAuth(req.headers.authorization);
|
|
151
|
+
var headerAuth = parseAuth(req.headers.authorization || req.headers['proxy-authorization']);
|
|
152
152
|
var queryAuth = parseAuth(req.query.authorization);
|
|
153
153
|
if (!isGuest && config.encrypted) {
|
|
154
154
|
headerAuth.pass = headerAuth.pass && createHash(headerAuth.pass);
|
|
@@ -330,7 +330,7 @@ function checkInternalPath(req) {
|
|
|
330
330
|
if (config.allowAllOrigin || ALLOW_CROSS_URLS.indexOf(req.path) !== -1) {
|
|
331
331
|
return true;
|
|
332
332
|
}
|
|
333
|
-
var host = req.headers[
|
|
333
|
+
var host = req.headers[common.ORIGIN_HOST_HEADER];
|
|
334
334
|
return !host || isAllowHost(host);
|
|
335
335
|
}
|
|
336
336
|
|
|
@@ -343,7 +343,7 @@ function checkAllowOrigin(req) {
|
|
|
343
343
|
if (config.allowAllOrigin) {
|
|
344
344
|
return true;
|
|
345
345
|
}
|
|
346
|
-
if (
|
|
346
|
+
if (CORS_PATHS.indexOf(req.path) !== -1) {
|
|
347
347
|
return true;
|
|
348
348
|
}
|
|
349
349
|
if (!config.allowOrigin) {
|
|
@@ -358,7 +358,7 @@ function checkAllowOrigin(req) {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
function cgiHandler(req, res) {
|
|
361
|
-
if (
|
|
361
|
+
if (common.existsUpPath(req.path) || !checkInternalPath(req)) {
|
|
362
362
|
return res.status(403).end('Forbidden');
|
|
363
363
|
}
|
|
364
364
|
if (checkAllowOrigin(req)) {
|
|
@@ -435,9 +435,8 @@ app.all(PLUGIN_PATH_RE, function(req, res) {
|
|
|
435
435
|
if (req.url.indexOf(INSPECTOR_URL) !== -1) {
|
|
436
436
|
return readPluginPage(req, res, plugin, INSPECTOR_HTML, plugin[util.PLUGIN_INSPECTOR_CONFIG]);
|
|
437
437
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
delete req.headers['x-whistle-internal-id'];
|
|
438
|
+
if (req.headers[util.INTERNAL_ID_HEADER] === util.INTERNAL_ID) {
|
|
439
|
+
delete req.headers[util.INTERNAL_ID_HEADER];
|
|
441
440
|
} else if (plugin.inheritAuth && !checkAuth(req, res)) {
|
|
442
441
|
return;
|
|
443
442
|
}
|
|
@@ -451,8 +450,6 @@ app.all(PLUGIN_PATH_RE, function(req, res) {
|
|
|
451
450
|
var options = parseReqUrl(req);
|
|
452
451
|
var headers = req.headers;
|
|
453
452
|
headers[config.PLUGIN_HOOK_NAME_HEADER] = config.PLUGIN_HOOKS.UI;
|
|
454
|
-
headers['x-whistle-remote-address'] = req._remoteAddr || util.getRemoteAddr(req);
|
|
455
|
-
headers['x-whistle-remote-port'] = req._remotePort || util.getRemotePort(req);
|
|
456
453
|
req.url = options.path.replace(result[0].slice(0, -1), '');
|
|
457
454
|
var openInModal = req.url.indexOf('?openInModal=5b6af7b9884e1165') !== -1;
|
|
458
455
|
util.transformReq(req, res, ports.uiPort, null, openInModal ? MODAL_HTML : null);
|
|
@@ -488,8 +485,8 @@ app.use(function(req, res, next) {
|
|
|
488
485
|
});
|
|
489
486
|
|
|
490
487
|
app.post('/cgi-bin/composer', function(req, res) {
|
|
491
|
-
req.headers[config.
|
|
492
|
-
req.headers[config.
|
|
488
|
+
req.headers[config.CLIENT_IP_HEADER] = util.getClientIp(req);
|
|
489
|
+
req.headers[config.CLIENT_PORT_HEADER] = util.getClientPort(req);
|
|
493
490
|
sendToService(req, res);
|
|
494
491
|
});
|
|
495
492
|
app.get('/cgi-bin/compose-data', sendToService);
|
|
@@ -557,7 +554,7 @@ app.all('/cgi-bin/*', function(req, res, next) {
|
|
|
557
554
|
}, cgiHandler);
|
|
558
555
|
|
|
559
556
|
app.use('/preview.html', function(req, res, next) {
|
|
560
|
-
if (req.headers[
|
|
557
|
+
if (req.headers[util.INTERNAL_ID_HEADER] !== util.INTERNAL_ID) {
|
|
561
558
|
return res.status(404).end('Not Found');
|
|
562
559
|
}
|
|
563
560
|
next();
|
package/lib/config.js
CHANGED
|
@@ -29,8 +29,7 @@ var disableAgent = version[0] > 10;
|
|
|
29
29
|
var useVerbatim = version[0] >= 17;
|
|
30
30
|
var hasIPv6First = version[0] >= 22 && version[1] >= 1;
|
|
31
31
|
var hasDnsOrder = typeof dns.setDefaultResultOrder === 'function';
|
|
32
|
-
var uid =
|
|
33
|
-
Date.now() + '-' + process.pid + '-' + Math.floor(Math.random() * 10000);
|
|
32
|
+
var uid = Date.now().toString(16) + (process.pid * 10000 + Math.floor(Math.random() * 10000)).toString(16);
|
|
34
33
|
var IPV4_RE = /^([\d.]+)(:\d{1,5})?$/;
|
|
35
34
|
var IPV6_RE = /^\[([\w:]+)\](:\d{1,5})?$/;
|
|
36
35
|
var noop = function () {};
|
|
@@ -75,39 +74,23 @@ config.uid = uid;
|
|
|
75
74
|
config.rejectUnauthorized = false;
|
|
76
75
|
config.enableH2 = version[0] > 12; // 支持 HTTP2 要求的最低 Node 版本
|
|
77
76
|
config.ASSESTS_PATH = path.join(__dirname, '../assets');
|
|
78
|
-
config.REQ_FROM_HEADER = 'x-whistle-request-from';
|
|
79
|
-
config.DISABLE_RULES_HEADER = 'x-whistle-proxy-rules-' + uid;
|
|
80
77
|
config.WHISTLE_POLICY_HEADER = 'x-whistle-policy';
|
|
81
|
-
config.
|
|
82
|
-
config.
|
|
83
|
-
config.
|
|
84
|
-
config.
|
|
85
|
-
config.HTTPS_PROTO_HEADER = 'x-forwarded-proto';
|
|
86
|
-
config.REAL_HOST_HEADER = common.REAL_HOST_HEADER;
|
|
87
|
-
config.FWD_HOST_HEADER = 'x-forwarded-host';
|
|
88
|
-
config.INTERNAL_ID =
|
|
89
|
-
Date.now() + '/' + process.pid + '/' + Math.floor(Math.random() * 100000);
|
|
90
|
-
config.INTERNAL_ID_HEADER = '_x-whistle-internal-id';
|
|
91
|
-
config.SNI_PLUGIN_HEADER = 'x-whistle-sni-callback-plugin-' + uid;
|
|
92
|
-
config.PROXY_ID_HEADER = 'x-whistle-proxy-id-' + uid;
|
|
93
|
-
config.CLIENT_PORT_HEAD = 'x-whistle-client-port';
|
|
94
|
-
config.WEBUI_HEAD = 'x-forwarded-from-whistle-' + uid;
|
|
95
|
-
config.RES_RULES_HEAD = 'x-whistle-res-rules-' + uid;
|
|
96
|
-
config.CLIENT_INFO_HEAD = 'x-whistle-client-info-' + uid;
|
|
97
|
-
config.REMOTE_ADDR_HEAD = 'x-whistle-remote-address-' + uid;
|
|
98
|
-
config.REMOTE_PORT_HEAD = 'x-whistle-remote-port-' + uid;
|
|
99
|
-
config.ADDITIONAL_HEAD = 'x-whistle-additional-headers';
|
|
78
|
+
config.CLIENT_IP_HEADER = common.CLIENT_IP_HEADER;
|
|
79
|
+
config.HTTPS_FIELD = common.HTTPS_FIELD;
|
|
80
|
+
config.CLIENT_INFO_HEADER = 'x-whistle-client-info-' + uid;
|
|
81
|
+
config.SHOW_LOGIN_BOX = 'x-whistle-show-login-box-' + uid;
|
|
100
82
|
config.WEBUI_PATH = '/.whistle-path.5b6af7b9884e1165/';
|
|
101
83
|
config.PREVIEW_PATH_RE = /\?\?\?WHISTLE_PREVIEW_CHARSET=([A-Z\d_-]+)\?\?\?$/;
|
|
102
84
|
config.PLUGIN_HOOK_NAME_HEADER = 'x-whistle-plugin-hook-name_';
|
|
103
|
-
config.CLIENT_ID_HEADER =
|
|
104
|
-
config.
|
|
85
|
+
config.CLIENT_ID_HEADER = common.CLIENT_ID_HEADER;
|
|
86
|
+
config.CLIENT_PORT_HEADER = common.CLIENT_PORT_HEADER;
|
|
87
|
+
config.ALPN_PROTOCOL_HEADER = common.ALPN_PROTOCOL_HEADER;
|
|
88
|
+
config.DISABLE_RULES_HEADER = 'x-whistle-proxy-rules-' + uid;
|
|
89
|
+
config.PROXY_ID_HEADER = 'x-whistle-proxy-id-' + uid;
|
|
90
|
+
config.WEBUI_HEAD = 'x-forwarded-from-whistle-' + uid;
|
|
91
|
+
config.RES_RULES_HEAD = 'x-whistle-res-rules-' + uid;
|
|
105
92
|
config.COMPOSER_CLIENT_ID_HEADER = 'x-whistle-client-id-' + uid;
|
|
106
|
-
config.
|
|
107
|
-
config.TEMP_TUNNEL_DATA_HEADER = 'x-whistle-tunnel-data-' + uid;
|
|
108
|
-
config.UPGRADE_HEADER = 'x-whistle-upgrade-' + uid;
|
|
109
|
-
config.SNI_TYPE_HEADER = 'x-whistle-sni-type-' + uid;
|
|
110
|
-
config.ALPN_PROTOCOL_HEADER = 'x-whistle-alpn-protocol';
|
|
93
|
+
config.FROM_COM_HEADER = 'x-whistle-composer-' + uid;
|
|
111
94
|
config.PLUGIN_HOOKS = {
|
|
112
95
|
SNI: 'sni-' + uid,
|
|
113
96
|
AUTH: 'auth-' + uid,
|
|
@@ -140,15 +123,11 @@ var DOMAIN_STAR_RE = /([*~]+)(\\.)?/g;
|
|
|
140
123
|
|
|
141
124
|
config.CONN_TIMEOUT = CONN_TIMEOUT;
|
|
142
125
|
|
|
143
|
-
var getHomedir = common.getHomedir;
|
|
144
|
-
var getHomePath = common.getHomePath;
|
|
145
126
|
var getWhistlePath = common.getWhistlePath;
|
|
146
127
|
var getHostPort = common.getHostPort;
|
|
147
128
|
|
|
148
129
|
var ALL_WHISTLES_PATH = path.join(getWhistlePath(), 'all_whistles');
|
|
149
130
|
|
|
150
|
-
config.getHomedir = getHomedir;
|
|
151
|
-
config.getHomePath = getHomePath;
|
|
152
131
|
|
|
153
132
|
function domainToRegExp(_, star, dot) {
|
|
154
133
|
var len = star.length;
|
|
@@ -870,7 +849,7 @@ exports.extend = function (newConf) {
|
|
|
870
849
|
config.pureProxy = true;
|
|
871
850
|
} else if (
|
|
872
851
|
m === 'keepXFF' ||
|
|
873
|
-
m ===
|
|
852
|
+
m === common.CLIENT_IP_HEADER ||
|
|
874
853
|
m === 'forwardedFor'
|
|
875
854
|
) {
|
|
876
855
|
config.keepXFF = true;
|
|
@@ -884,6 +863,8 @@ exports.extend = function (newConf) {
|
|
|
884
863
|
process.env.PFORK_EXEC_PATH = process.execPath;
|
|
885
864
|
} else if (m === 'safe' || m === 'rejectUnauthorized') {
|
|
886
865
|
config.rejectUnauthorized = true;
|
|
866
|
+
} else if (m === 'enableRequestHeaderRules') {
|
|
867
|
+
config.enableRequestHeaderRules = true;
|
|
887
868
|
} else if (m === 'persistentCapture') {
|
|
888
869
|
config.isEnableCapture = true;
|
|
889
870
|
config.persistentCapture = true;
|
|
@@ -1139,7 +1120,7 @@ exports.extend = function (newConf) {
|
|
|
1139
1120
|
return agent;
|
|
1140
1121
|
};
|
|
1141
1122
|
var baseDir = config.baseDir
|
|
1142
|
-
? path.resolve(getHomePath(config.baseDir), config.dataDirname)
|
|
1123
|
+
? path.resolve(common.getHomePath(config.baseDir), config.dataDirname)
|
|
1143
1124
|
: (config.whistleName ? path.join(ALL_WHISTLES_PATH, config.whistleName) : getDataDir(config.dataDirname));
|
|
1144
1125
|
var customDirs = path.join(baseDir, 'custom_dirs');
|
|
1145
1126
|
config.baseDir = baseDir;
|
|
@@ -26,6 +26,7 @@ var HASH_RE = /#.*$/;
|
|
|
26
26
|
var CROSS_RE = /^x(s)?/;
|
|
27
27
|
var HTTP_RE = /^http:/;
|
|
28
28
|
var NOT_A_FILE_ERR = new Error('Not a file');
|
|
29
|
+
var INVALID_PATH = '(Path contains parent directory notation \'..\')';
|
|
29
30
|
|
|
30
31
|
function urlToStr(url) {
|
|
31
32
|
return url.replace(SLASH_RE, '').replace(QUOTE_RE, '\\$&').replace(BLANK_RE, ' ');
|
|
@@ -48,7 +49,7 @@ function readFiles(files, callback) {
|
|
|
48
49
|
} else if (files.length) {
|
|
49
50
|
readFiles(files, callback);
|
|
50
51
|
} else {
|
|
51
|
-
callback(err || NOT_A_FILE_ERR, file);
|
|
52
|
+
callback(err || NOT_A_FILE_ERR, file == null ? INVALID_PATH : file);
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
|
|
@@ -35,7 +35,7 @@ module.exports = function (req, res, next) {
|
|
|
35
35
|
}
|
|
36
36
|
var fullUrl = req.options._realUrl || req.fullUrl;
|
|
37
37
|
var options = util.parseUrl(fullUrl);
|
|
38
|
-
pluginMgr.
|
|
38
|
+
pluginMgr.addSessionInfo(req, req.rules);
|
|
39
39
|
options.protocol = 'http:';
|
|
40
40
|
options.host = '127.0.0.1';
|
|
41
41
|
options.port = ports.port;
|
package/lib/https/ca.js
CHANGED
|
@@ -12,6 +12,7 @@ var createSecureContext =
|
|
|
12
12
|
var util = require('../util');
|
|
13
13
|
var config = require('../config');
|
|
14
14
|
var common = require('../util/common');
|
|
15
|
+
var rulesUtil = require('../rules/util');
|
|
15
16
|
|
|
16
17
|
var pki = forge.pki;
|
|
17
18
|
var CUR_VERSION = process.version;
|
|
@@ -51,11 +52,20 @@ var pureCertFiles = {};
|
|
|
51
52
|
var customRoot;
|
|
52
53
|
var ROOT_KEY, ROOT_CRT;
|
|
53
54
|
var rootKey, rootCrt;
|
|
55
|
+
var ILLEGAL_PATH_RE = /[/\\]/;
|
|
54
56
|
|
|
55
57
|
exports.remoteCerts = remoteCerts;
|
|
56
58
|
exports.createSecureContext = createSecureContext;
|
|
57
59
|
exports.CUSTOM_CERTS_DIR = CUSTOM_CERTS_DIR;
|
|
58
60
|
|
|
61
|
+
function isRootCa(name) {
|
|
62
|
+
return name !== 'root';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function checkFilename(name) {
|
|
66
|
+
return name && !ILLEGAL_PATH_RE.test(name) && isRootCa(name);
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
function resetAllCerts() {
|
|
60
70
|
cachePairs.reset();
|
|
61
71
|
certsCache.reset();
|
|
@@ -255,27 +265,30 @@ function parseAllCustomCerts() {
|
|
|
255
265
|
var validity = info.validity;
|
|
256
266
|
var altNames = info.altNames;
|
|
257
267
|
var dnsName = [];
|
|
268
|
+
var disabled = (isRootCa(filename) && rulesUtil.isDisabledCertFile(filename)) || undefined;
|
|
258
269
|
altNames.forEach(function (item) {
|
|
259
270
|
if ((item.type === 2 || item.type === 7) && !pairs[item.value]) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (preCert.
|
|
263
|
-
preCert.mtime
|
|
271
|
+
if (!disabled) {
|
|
272
|
+
var preCert = customPairs[item.value];
|
|
273
|
+
if (preCert && preCert.key === cert.key && preCert.cert === cert.cert) {
|
|
274
|
+
if (preCert.mtime < mtime) {
|
|
275
|
+
preCert.mtime = mtime;
|
|
276
|
+
}
|
|
277
|
+
pairs[item.value] = preCert;
|
|
278
|
+
} else {
|
|
279
|
+
pairs[item.value] = cert;
|
|
264
280
|
}
|
|
265
|
-
pairs[item.value] = preCert;
|
|
266
|
-
} else {
|
|
267
|
-
pairs[item.value] = cert;
|
|
268
281
|
}
|
|
269
282
|
dnsName.push(item.value);
|
|
270
283
|
certsInfo[item.value] = extend(
|
|
271
|
-
{ filename: filename, type: cert.type, mtime: mtime, domain: item.value },
|
|
284
|
+
{ filename: filename, type: cert.type, mtime: mtime, domain: item.value, disabled: disabled },
|
|
272
285
|
validity
|
|
273
286
|
);
|
|
274
287
|
}
|
|
275
288
|
});
|
|
276
289
|
if (dnsName.length) {
|
|
277
290
|
certFiles[filename] = extend(
|
|
278
|
-
{ mtime: mtime, type: cert.type, dir: cert.dir, dnsName: dnsName.join(', ') },
|
|
291
|
+
{ mtime: mtime, type: cert.type, dir: cert.dir, dnsName: dnsName.join(', '), disabled: disabled },
|
|
279
292
|
validity
|
|
280
293
|
);
|
|
281
294
|
}
|
|
@@ -424,22 +437,11 @@ function createRootCA() {
|
|
|
424
437
|
}
|
|
425
438
|
}
|
|
426
439
|
|
|
427
|
-
function getRandom() {
|
|
428
|
-
var random = Math.floor(Math.random() * 1000);
|
|
429
|
-
if (random < 10) {
|
|
430
|
-
return '00' + random;
|
|
431
|
-
}
|
|
432
|
-
if (random < 100) {
|
|
433
|
-
return '0' + random;
|
|
434
|
-
}
|
|
435
|
-
return '' + random;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
440
|
function createCACert(opts) {
|
|
439
441
|
opts = opts || {};
|
|
440
442
|
var keys = pki.rsa.generateKeyPair(requiredVersion ? 2048 : 1024);
|
|
441
443
|
var cert = createCert(keys.publicKey);
|
|
442
|
-
var now = Date.now() +
|
|
444
|
+
var now = Date.now() + common.padLeft(Math.floor(Math.random() * 1000), 3);
|
|
443
445
|
var attrs = [
|
|
444
446
|
{
|
|
445
447
|
name: 'commonName',
|
|
@@ -730,12 +732,6 @@ function writeCertFile(filename, type, cert, mtime) {
|
|
|
730
732
|
});
|
|
731
733
|
}
|
|
732
734
|
|
|
733
|
-
var ILLEGAL_PATH_RE = /[/\\]/;
|
|
734
|
-
|
|
735
|
-
function checkFilename(name) {
|
|
736
|
-
return name && !ILLEGAL_PATH_RE.test(name) && name !== 'root';
|
|
737
|
-
}
|
|
738
|
-
|
|
739
735
|
function getCertType(type) {
|
|
740
736
|
if (type !== 'cer' && type !== 'pem') {
|
|
741
737
|
return 'crt';
|
|
@@ -756,6 +752,17 @@ exports.removeCert = function (opts) {
|
|
|
756
752
|
}
|
|
757
753
|
};
|
|
758
754
|
|
|
755
|
+
exports.setActiveCert = function (opts) {
|
|
756
|
+
if (!CUSTOM_CERTS_DIR) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
var filename = opts.filename;
|
|
760
|
+
if (isRootCa(filename) && allCustomCerts[filename]) {
|
|
761
|
+
rulesUtil.setDisabledCertFile(filename, opts.disabled);
|
|
762
|
+
parseAllCustomCerts();
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
|
|
759
766
|
exports.uploadCerts = function (certs) {
|
|
760
767
|
if (!CUSTOM_CERTS_DIR) {
|
|
761
768
|
return;
|
package/lib/https/h2.js
CHANGED
|
@@ -296,7 +296,7 @@ function requestH2(client, req, res, callback) {
|
|
|
296
296
|
var statusCode = h2Headers[':status'];
|
|
297
297
|
var svrRes = h2Session;
|
|
298
298
|
if (additionalHeaders) {
|
|
299
|
-
newHeaders[
|
|
299
|
+
newHeaders[util.ADDITIONAL_HEAD] = additionalHeaders;
|
|
300
300
|
}
|
|
301
301
|
svrRes.on('trailers', function (trailers) {
|
|
302
302
|
svrRes.trailers = trailers;
|
package/lib/https/index.js
CHANGED
|
@@ -36,6 +36,7 @@ var CONN_TIMEOUT = 60000;
|
|
|
36
36
|
var X_RE = /^x/;
|
|
37
37
|
var PROTO_SEP_RE = /,\s*/;
|
|
38
38
|
var HTTP_PROTO_RE = /^(ws|http)s?:\/\//;
|
|
39
|
+
var clientIpKey = config.CLIENT_IP_HEADER;
|
|
39
40
|
var proxy, server;
|
|
40
41
|
|
|
41
42
|
function handleWebsocket(socket, clientIp, clientPort) {
|
|
@@ -274,7 +275,7 @@ function resolveWebsocket(socket, wss) {
|
|
|
274
275
|
options.port = port;
|
|
275
276
|
data.realUrl = util.changePort(fullUrl, options.port);
|
|
276
277
|
socket.customParser = util.getParserStatus(socket);
|
|
277
|
-
pluginMgr.
|
|
278
|
+
pluginMgr.addSessionInfo(socket, _rules);
|
|
278
279
|
socketMgr.setPending(socket);
|
|
279
280
|
options.protocol = 'ws:';
|
|
280
281
|
socket.headers[config.PLUGIN_HOOK_NAME_HEADER] =
|
|
@@ -441,7 +442,7 @@ function resolveWebsocket(socket, wss) {
|
|
|
441
442
|
wss = false;
|
|
442
443
|
}
|
|
443
444
|
if (!util.isLocalAddress(clientIp)) {
|
|
444
|
-
proxyHeaders[
|
|
445
|
+
proxyHeaders[clientIpKey] = clientIp;
|
|
445
446
|
}
|
|
446
447
|
if (isProxyPort) {
|
|
447
448
|
proxyHeaders[config.WEBUI_HEAD] = 1;
|
|
@@ -635,13 +636,13 @@ function resolveWebsocket(socket, wss) {
|
|
|
635
636
|
util.setClientId(headers, enable, disable, clientIp, isInternalProxy);
|
|
636
637
|
}
|
|
637
638
|
if (disable.clientIp || disable.clientIP) {
|
|
638
|
-
delete headers[
|
|
639
|
+
delete headers[clientIpKey];
|
|
639
640
|
} else {
|
|
640
641
|
var forwardedFor = util.getMatcherValue(_rules.forwardedFor);
|
|
641
642
|
if (net.isIP(forwardedFor)) {
|
|
642
|
-
headers[
|
|
643
|
-
} else if (
|
|
644
|
-
headers[
|
|
643
|
+
headers[clientIpKey] = forwardedFor;
|
|
644
|
+
} else if (socket._customXFF) {
|
|
645
|
+
headers[clientIpKey] = socket._customXFF;
|
|
645
646
|
} else if (
|
|
646
647
|
(!isInternalProxy &&
|
|
647
648
|
!plugin &&
|
|
@@ -650,9 +651,9 @@ function resolveWebsocket(socket, wss) {
|
|
|
650
651
|
!enable.clientIP) ||
|
|
651
652
|
util.isLocalAddress(clientIp)
|
|
652
653
|
) {
|
|
653
|
-
delete headers[
|
|
654
|
+
delete headers[clientIpKey];
|
|
654
655
|
} else {
|
|
655
|
-
headers[
|
|
656
|
+
headers[clientIpKey] = clientIp;
|
|
656
657
|
}
|
|
657
658
|
}
|
|
658
659
|
util.deleteReqHeaders(socket);
|
|
@@ -708,9 +709,12 @@ function resolveWebsocket(socket, wss) {
|
|
|
708
709
|
socket._origin = headers.origin;
|
|
709
710
|
if (reqHeaders) {
|
|
710
711
|
reqHeaders = util.lowerCaseify(reqHeaders, socket.rawHeaderNames);
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
712
|
+
var xff = reqHeaders[clientIpKey];
|
|
713
|
+
if (net.isIP(xff)) {
|
|
714
|
+
socket._customXFF = xff;
|
|
715
|
+
}
|
|
716
|
+
socket._customClientId = reqHeaders[config.CLIENT_ID_HEADER];
|
|
717
|
+
delete reqHeaders[clientIpKey];
|
|
714
718
|
extend(headers, reqHeaders);
|
|
715
719
|
headers.host = headers.host || host;
|
|
716
720
|
}
|
|
@@ -900,7 +904,7 @@ function getTunnelData(socket, clientIp, clientPort, isHttpH2) {
|
|
|
900
904
|
var enable = socket.enable || '';
|
|
901
905
|
var disable = socket.disable || '';
|
|
902
906
|
var headers = socket.headers;
|
|
903
|
-
var tunnelData = headers[
|
|
907
|
+
var tunnelData = headers[util.TUNNEL_DATA_HEADER];
|
|
904
908
|
var tdKey = config.tdKey;
|
|
905
909
|
if (tdKey && (!tunnelData || config.overTdKey)) {
|
|
906
910
|
tunnelData = headers[tdKey] || tunnelData;
|
|
@@ -944,7 +948,7 @@ function addReqInfo(req) {
|
|
|
944
948
|
req.reqId = remoteData.id;
|
|
945
949
|
socket._remoteDataInfo = remoteData;
|
|
946
950
|
remoteData._connected = true;
|
|
947
|
-
headers[config.
|
|
951
|
+
headers[config.CLIENT_INFO_HEADER] =
|
|
948
952
|
(remoteData.clientIp || LOCALHOST) +
|
|
949
953
|
',' + remoteData.clientPort +
|
|
950
954
|
',' + remoteData.remoteAddr +
|
|
@@ -1058,9 +1062,9 @@ function toHttp1(req, res) {
|
|
|
1058
1062
|
} catch (e) {}
|
|
1059
1063
|
}
|
|
1060
1064
|
});
|
|
1061
|
-
var addHeaders = svrRes.headers[
|
|
1065
|
+
var addHeaders = svrRes.headers[util.ADDITIONAL_HEAD];
|
|
1062
1066
|
if (addHeaders) {
|
|
1063
|
-
delete svrRes.headers[
|
|
1067
|
+
delete svrRes.headers[util.ADDITIONAL_HEAD];
|
|
1064
1068
|
if (isH2 && res.stream && typeof res.stream.additionalHeaders === 'function'
|
|
1065
1069
|
&& (addHeaders = util.parseRawJson(addHeaders))) {
|
|
1066
1070
|
try {
|
|
@@ -1117,7 +1121,7 @@ function addClientInfo(socket, chunk, statusLine, clientIp, clientPort) {
|
|
|
1117
1121
|
chunk = chunk.slice(len);
|
|
1118
1122
|
statusLine +=
|
|
1119
1123
|
'\r\n' +
|
|
1120
|
-
config.
|
|
1124
|
+
config.CLIENT_INFO_HEADER +
|
|
1121
1125
|
': ' +
|
|
1122
1126
|
clientIp +
|
|
1123
1127
|
',' +
|
|
@@ -1128,7 +1132,7 @@ function addClientInfo(socket, chunk, statusLine, clientIp, clientPort) {
|
|
|
1128
1132
|
socket._remotePort;
|
|
1129
1133
|
var tunnelData = getTunnelData(socket, clientIp, clientPort);
|
|
1130
1134
|
if (tunnelData) {
|
|
1131
|
-
statusLine += '\r\n' +
|
|
1135
|
+
statusLine += '\r\n' + util.TEMP_TUNNEL_DATA_HEADER + ': ' + encodeURIComponent(JSON.stringify(tunnelData));
|
|
1132
1136
|
}
|
|
1133
1137
|
return Buffer.concat([Buffer.from(statusLine), chunk]);
|
|
1134
1138
|
}
|
package/lib/index.js
CHANGED
|
@@ -162,7 +162,7 @@ function proxy(callback, _server) {
|
|
|
162
162
|
var clientIp = util.removeIPV6Prefix(info.srcAddr) || '127.0.0.1';
|
|
163
163
|
var clientPort = info.srcPort;
|
|
164
164
|
headers['x-whistle-server'] = 'socks';
|
|
165
|
-
headers[config.
|
|
165
|
+
headers[config.CLIENT_INFO_HEADER] = [clientIp, clientPort, clientIp, clientPort].join();
|
|
166
166
|
if (config.socksMode) {
|
|
167
167
|
headers[config.WHISTLE_POLICY_HEADER] = 'weakTunnel';
|
|
168
168
|
}
|
package/lib/init.js
CHANGED
|
@@ -4,6 +4,7 @@ var socketMgr = require('./socket-mgr');
|
|
|
4
4
|
var config = require('./config');
|
|
5
5
|
|
|
6
6
|
var HTTPS_RE = /^https:/i;
|
|
7
|
+
var clientIpKey = config.CLIENT_IP_HEADER;
|
|
7
8
|
|
|
8
9
|
function addErrorEvents(req, res) {
|
|
9
10
|
++util.proc.allHttpRequests;
|
|
@@ -166,36 +167,36 @@ module.exports = function (req, res, next) {
|
|
|
166
167
|
req._remoteAddr = clientInfo[2] || util.getRemoteAddr(req);
|
|
167
168
|
req._remotePort = clientInfo[3] || util.getRemotePort(req);
|
|
168
169
|
if (clientIp && util.isLocalAddress(clientIp)) {
|
|
169
|
-
delete headers[
|
|
170
|
+
delete headers[clientIpKey];
|
|
170
171
|
clientIp = null;
|
|
171
172
|
}
|
|
172
|
-
if (!socket[
|
|
173
|
-
socket[
|
|
173
|
+
if (!socket[clientIpKey]) {
|
|
174
|
+
socket[clientIpKey] = clientIp || util.getClientIp(req);
|
|
174
175
|
}
|
|
175
|
-
req.clientIp = clientIp = clientIp || socket[
|
|
176
|
+
req.clientIp = clientIp = clientIp || socket[clientIpKey];
|
|
176
177
|
req.method = util.getMethod(req.method);
|
|
177
178
|
req._clientId = util.getComposerClientId(headers);
|
|
178
|
-
var clientPort = clientInfo[1] || headers[config.
|
|
179
|
-
delete headers[config.
|
|
179
|
+
var clientPort = clientInfo[1] || headers[config.CLIENT_PORT_HEADER];
|
|
180
|
+
delete headers[config.CLIENT_PORT_HEADER];
|
|
180
181
|
if (!(clientPort > 0)) {
|
|
181
182
|
clientPort = null;
|
|
182
183
|
}
|
|
183
|
-
if (!socket[config.
|
|
184
|
-
socket[config.
|
|
184
|
+
if (!socket[config.CLIENT_PORT_HEADER]) {
|
|
185
|
+
socket[config.CLIENT_PORT_HEADER] = clientPort || socket.remotePort;
|
|
185
186
|
}
|
|
186
|
-
req.clientPort = clientPort = clientPort || socket[config.
|
|
187
|
+
req.clientPort = clientPort = clientPort || socket[config.CLIENT_PORT_HEADER];
|
|
187
188
|
util.handleForwardedProps(req);
|
|
188
189
|
var isHttps =
|
|
189
190
|
req.socket.isHttps || req.isHttps || headers[config.HTTPS_FIELD];
|
|
190
191
|
if (isHttps) {
|
|
191
192
|
req.isHttps = true;
|
|
192
193
|
delete headers[config.HTTPS_FIELD];
|
|
193
|
-
delete headers[
|
|
194
|
+
delete headers[util.HTTPS_PROTO_HEADER];
|
|
194
195
|
}
|
|
195
|
-
var sniPlugin = headers[
|
|
196
|
+
var sniPlugin = headers[util.SNI_PLUGIN_HEADER];
|
|
196
197
|
if (sniPlugin) {
|
|
197
198
|
req.sniPlugin = sniPlugin;
|
|
198
|
-
delete headers[
|
|
199
|
+
delete headers[util.SNI_PLUGIN_HEADER];
|
|
199
200
|
}
|
|
200
201
|
if (!req.isHttps && HTTPS_RE.test(req.url)) {
|
|
201
202
|
req.isHttps = true;
|
package/lib/inspectors/req.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
var qs = require('querystring');
|
|
2
2
|
var iconv = require('iconv-lite');
|
|
3
|
+
var net = require('net');
|
|
3
4
|
var util = require('../util');
|
|
4
5
|
var extend = require('extend');
|
|
5
6
|
var mime = require('mime');
|
|
@@ -25,6 +26,7 @@ var LF = CRLF[1];
|
|
|
25
26
|
var LINE = Buffer.from('-')[0];
|
|
26
27
|
var UPLOAD_CTN_SEP = Buffer.from('\r\n\r\n');
|
|
27
28
|
var CTN_DIS = 'Content-Disposition: form-data; name="';
|
|
29
|
+
var clientIpKey = config.CLIENT_IP_HEADER;
|
|
28
30
|
|
|
29
31
|
var REQ_TYPE = {
|
|
30
32
|
urlencoded: 'application/x-www-form-urlencoded',
|
|
@@ -129,17 +131,18 @@ function handleReq(req, data, reqRules, delType) {
|
|
|
129
131
|
var opList = util.parseHeaderReplace(req.rules.headerReplace).req;
|
|
130
132
|
if (opList) {
|
|
131
133
|
var host = req.headers.host;
|
|
132
|
-
var xff = req.headers[
|
|
133
|
-
var clientId = req.headers[config.
|
|
134
|
+
var xff = req.headers[clientIpKey];
|
|
135
|
+
var clientId = req.headers[config.CLIENT_ID_HEADER];
|
|
134
136
|
util.handleHeaderReplace(req.headers, opList);
|
|
135
137
|
if (req.headers.host !== host) {
|
|
136
138
|
req._customHost = req.headers.host;
|
|
137
139
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
var newXFF = req.headers[clientIpKey];
|
|
141
|
+
if (xff !== newXFF && net.isIP(newXFF)) {
|
|
142
|
+
req._customXFF = newXFF;
|
|
140
143
|
}
|
|
141
|
-
if (clientId !== req.headers[config.
|
|
142
|
-
req._customClientId = req.headers[config.
|
|
144
|
+
if (clientId !== req.headers[config.CLIENT_ID_HEADER]) {
|
|
145
|
+
req._customClientId = req.headers[config.CLIENT_ID_HEADER];
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
}
|
|
@@ -486,8 +489,8 @@ module.exports = function (req, res, next) {
|
|
|
486
489
|
if (data.headers) {
|
|
487
490
|
data.headers = util.lowerCaseify(data.headers, req.rawHeaderNames);
|
|
488
491
|
req._customHost = data.headers.host;
|
|
489
|
-
req._customXFF = data.headers[
|
|
490
|
-
req._customClientId = data.headers[config.
|
|
492
|
+
req._customXFF = data.headers[clientIpKey];
|
|
493
|
+
req._customClientId = data.headers[config.CLIENT_ID_HEADER];
|
|
491
494
|
if (typeof data.headers['content-type'] !== 'string') {
|
|
492
495
|
delete data.headers['content-type'];
|
|
493
496
|
}
|
|
@@ -575,10 +578,10 @@ module.exports = function (req, res, next) {
|
|
|
575
578
|
util.disableReqProps(req);
|
|
576
579
|
handleReplace(req, replacement);
|
|
577
580
|
var reqWriter = util.hasRequestBody(req)
|
|
578
|
-
? util.
|
|
581
|
+
? util.getWriteFilePath(reqRules.reqWrite)
|
|
579
582
|
: null;
|
|
580
583
|
util.getFileWriters(
|
|
581
|
-
[reqWriter, util.
|
|
584
|
+
[reqWriter, util.getWriteFilePath(reqRules.reqWriteRaw)],
|
|
582
585
|
function (writer, rawWriter) {
|
|
583
586
|
if (writer) {
|
|
584
587
|
req.addZipTransform(
|