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.
Files changed (41) hide show
  1. package/assets/js/log.js +8 -4
  2. package/bin/util.js +1 -1
  3. package/biz/index.js +12 -10
  4. package/biz/webui/cgi-bin/certs/active.js +6 -0
  5. package/biz/webui/cgi-bin/certs/remove.js +0 -1
  6. package/biz/webui/cgi-bin/certs/upload.js +0 -1
  7. package/biz/webui/cgi-bin/util.js +7 -20
  8. package/biz/webui/htdocs/index.html +1 -1
  9. package/biz/webui/htdocs/js/index.js +43 -43
  10. package/biz/webui/lib/index.js +10 -13
  11. package/lib/config.js +17 -36
  12. package/lib/handlers/file-proxy.js +2 -1
  13. package/lib/handlers/http-proxy.js +1 -1
  14. package/lib/https/ca.js +34 -27
  15. package/lib/https/h2.js +1 -1
  16. package/lib/https/index.js +21 -17
  17. package/lib/index.js +1 -1
  18. package/lib/init.js +13 -12
  19. package/lib/inspectors/req.js +13 -10
  20. package/lib/inspectors/res.js +375 -390
  21. package/lib/inspectors/rules.js +11 -0
  22. package/lib/plugins/compat.js +108 -0
  23. package/lib/plugins/get-plugins-sync.js +1 -4
  24. package/lib/plugins/index.js +92 -284
  25. package/lib/plugins/load-plugin.js +205 -223
  26. package/lib/plugins/module-paths.js +3 -4
  27. package/lib/plugins/proxy.js +4 -7
  28. package/lib/rules/index.js +69 -66
  29. package/lib/rules/protocols.js +6 -0
  30. package/lib/rules/rules.js +44 -32
  31. package/lib/rules/util.js +30 -0
  32. package/lib/service/composer.js +5 -5
  33. package/lib/service/util.js +7 -21
  34. package/lib/tunnel.js +15 -12
  35. package/lib/upgrade.js +9 -10
  36. package/lib/util/common.js +171 -48
  37. package/lib/util/file-mgr.js +3 -7
  38. package/lib/util/index.js +101 -143
  39. package/package.json +1 -1
  40. package/README-en_US.md +0 -80
  41. package/biz/webui/htdocs/img/assistant.svg +0 -1
@@ -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['x-whistle-origin-host'];
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 (req.path === '/cgi-bin/rootca' || req.path === '/cgi-bin/status') {
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 (UP_PATH_REGEXP.test(req.path) || !checkInternalPath(req)) {
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
- var internalId = req.headers['x-whistle-internal-id'];
439
- if (internalId === util.INTERNAL_ID) {
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.CLIENT_IP_HEAD] = util.getClientIp(req);
492
- req.headers[config.CLIENT_PORT_HEAD] = util.getClientPort(req);
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[config.INTERNAL_ID_HEADER] !== config.INTERNAL_ID) {
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.CUSTOM_CERT_HEADER = 'x-whistle-exists-custom-cert';
82
- config.ENABLE_CAPTURE_HEADER = 'x-whistle-enable-capture';
83
- config.CLIENT_IP_HEAD = 'x-forwarded-for';
84
- config.HTTPS_FIELD = 'x-whistle-https-request';
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 = 'x-whistle-client-id';
104
- config.CLIENT_ID_HEAD = 'x-whistle-client-id';
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.TUNNEL_DATA_HEADER = 'x-whistle-tunnel-data';
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 === 'x-forwarded-for' ||
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.addRuleHeaders(req, req.rules);
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
- var preCert = customPairs[item.value];
261
- if (preCert && preCert.key === cert.key && preCert.cert === cert.cert) {
262
- if (preCert.mtime < mtime) {
263
- preCert.mtime = 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() + getRandom();
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[config.ADDITIONAL_HEAD] = additionalHeaders;
299
+ newHeaders[util.ADDITIONAL_HEAD] = additionalHeaders;
300
300
  }
301
301
  svrRes.on('trailers', function (trailers) {
302
302
  svrRes.trailers = trailers;
@@ -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.addRuleHeaders(socket, _rules);
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[config.CLIENT_IP_HEAD] = clientIp;
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[config.CLIENT_IP_HEAD];
639
+ delete headers[clientIpKey];
639
640
  } else {
640
641
  var forwardedFor = util.getMatcherValue(_rules.forwardedFor);
641
642
  if (net.isIP(forwardedFor)) {
642
- headers[config.CLIENT_IP_HEAD] = forwardedFor;
643
- } else if (net.isIP(socket._customXFF)) {
644
- headers[config.CLIENT_IP_HEAD] = socket._customXFF;
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[config.CLIENT_IP_HEAD];
654
+ delete headers[clientIpKey];
654
655
  } else {
655
- headers[config.CLIENT_IP_HEAD] = clientIp;
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
- socket._customXFF = reqHeaders[config.CLIENT_IP_HEAD];
712
- socket._customClientId = reqHeaders[config.CLIENT_ID_HEAD];
713
- delete reqHeaders[config.CLIENT_IP_HEAD];
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[config.TUNNEL_DATA_HEADER];
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.CLIENT_INFO_HEAD] =
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[config.ADDITIONAL_HEAD];
1065
+ var addHeaders = svrRes.headers[util.ADDITIONAL_HEAD];
1062
1066
  if (addHeaders) {
1063
- delete svrRes.headers[config.ADDITIONAL_HEAD];
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.CLIENT_INFO_HEAD +
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' + config.TEMP_TUNNEL_DATA_HEADER + ': ' + encodeURIComponent(JSON.stringify(tunnelData));
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.CLIENT_INFO_HEAD] = [clientIp, clientPort, clientIp, clientPort].join();
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[config.CLIENT_IP_HEAD];
170
+ delete headers[clientIpKey];
170
171
  clientIp = null;
171
172
  }
172
- if (!socket[config.CLIENT_IP_HEAD]) {
173
- socket[config.CLIENT_IP_HEAD] = clientIp || util.getClientIp(req);
173
+ if (!socket[clientIpKey]) {
174
+ socket[clientIpKey] = clientIp || util.getClientIp(req);
174
175
  }
175
- req.clientIp = clientIp = clientIp || socket[config.CLIENT_IP_HEAD];
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.CLIENT_PORT_HEAD];
179
- delete headers[config.CLIENT_PORT_HEAD];
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.CLIENT_PORT_HEAD]) {
184
- socket[config.CLIENT_PORT_HEAD] = clientPort || socket.remotePort;
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.CLIENT_PORT_HEAD];
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[config.HTTPS_PROTO_HEADER];
194
+ delete headers[util.HTTPS_PROTO_HEADER];
194
195
  }
195
- var sniPlugin = headers[config.SNI_PLUGIN_HEADER];
196
+ var sniPlugin = headers[util.SNI_PLUGIN_HEADER];
196
197
  if (sniPlugin) {
197
198
  req.sniPlugin = sniPlugin;
198
- delete headers[config.SNI_PLUGIN_HEADER];
199
+ delete headers[util.SNI_PLUGIN_HEADER];
199
200
  }
200
201
  if (!req.isHttps && HTTPS_RE.test(req.url)) {
201
202
  req.isHttps = true;
@@ -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[config.CLIENT_IP_HEAD];
133
- var clientId = req.headers[config.CLIENT_ID_HEAD];
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
- if (xff !== req.headers[config.CLIENT_IP_HEAD]) {
139
- req._customXFF = req.headers[config.CLIENT_IP_HEAD];
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.CLIENT_ID_HEAD]) {
142
- req._customClientId = req.headers[config.CLIENT_ID_HEAD];
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[config.CLIENT_IP_HEAD];
490
- req._customClientId = data.headers[config.CLIENT_ID_HEAD];
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.getRuleFile(reqRules.reqWrite)
581
+ ? util.getWriteFilePath(reqRules.reqWrite)
579
582
  : null;
580
583
  util.getFileWriters(
581
- [reqWriter, util.getRuleFile(reqRules.reqWriteRaw)],
584
+ [reqWriter, util.getWriteFilePath(reqRules.reqWriteRaw)],
582
585
  function (writer, rawWriter) {
583
586
  if (writer) {
584
587
  req.addZipTransform(