whistle 2.9.76 → 2.9.77

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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  [![Test coverage](https://codecov.io/gh/avwo/whistle/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/avwo/whistle)
11
11
  [![npm download](https://img.shields.io/npm/dm/whistle.svg?style=flat-square)](https://npmjs.org/package/whistle)
12
12
  [![NPM count](https://img.shields.io/npm/dt/whistle.svg?style=flat-square)](https://www.npmjs.com/package/whistle)
13
- [![License](https://img.shields.io/npm/l/whistle.svg?style=flat-square)](https://www.npmjs.com/package/whistle)
13
+ [![License](https://img.shields.io/aur/license/whistle?style=flat-square)](https://www.npmjs.com/package/whistle)
14
14
 
15
15
  **Mac 或 Windows 系统推荐使用客户端版本:https://github.com/avwo/whistle-client**
16
16
 
@@ -29,7 +29,8 @@ Whistle 是基于 Node 实现的跨平台抓包调试工具,其主要特点:
29
29
  * 项目可以自带代理规则配置并一键设置到本地 Whistle 代理,也可以通过定制插件简化操作
30
30
 
31
31
  # 一键安装
32
- > 已安装 `brew` 的 PC,可以省略以下 1、2 步骤,直接通过以下方式一键安装:`brew install whistle && w2 start --init`(arm64 平台尝试用 `brew install node && npm i -g whistle && w2 start --init`)
32
+ > 已安装 `brew` 的 PC,可以省略以下 1、2 步骤,直接通过以下方式一键安装:`brew install whistle && w2 start --init`
33
+
33
34
  1. 安装 Node(建议安装**最新的 LTS 版本**,如已安装忽略此步骤):https://nodejs.org/
34
35
  2. 一键安装,在命令行执行以下命令:
35
36
  ``` sh
package/assets/js/log.js CHANGED
@@ -220,6 +220,22 @@
220
220
 
221
221
  return obj === undefined ? 'undefined' : obj;
222
222
  }
223
+ var prefixPath;
224
+ function getPathPrefix() {
225
+ if (prefixPath) {
226
+ return prefixPath;
227
+ }
228
+ prefixPath = window.__WHISTLE_PATH_PREFIX__;
229
+ if (/^\/[\w./-]+$/.test(prefixPath) && prefixPath.length <= 128) {
230
+ var len = prefixPath.length - 1;
231
+ if (prefixPath[len] === '/') {
232
+ prefixPath = prefixPath.substring(0, len);
233
+ }
234
+ } else {
235
+ prefixPath = '';
236
+ }
237
+ return prefixPath;
238
+ }
223
239
 
224
240
  var index = 0;
225
241
  var MAX_LEN = 1024 * 56;
@@ -237,7 +253,8 @@
237
253
  logStr = logStr.substring(0, percIndex);
238
254
  }
239
255
  }
240
- img.src ='$LOG_CGI?id=$LOG_ID&level=' + level + '&text=' + logStr
256
+ var baseUrl = '$BASE_URL' + getPathPrefix();
257
+ img.src = baseUrl + '$LOG_CGI?id=$LOG_ID&level=' + level + '&text=' + logStr
241
258
  + '&t=' + new Date().getTime() + '&' + ++index;
242
259
  var preventGC = function() {
243
260
  img.onload = img.onerror = null;
@@ -3,12 +3,22 @@
3
3
  if (typeof window === 'undefined' || window.WeinreServerURL) {
4
4
  return;
5
5
  }
6
- window.WeinreServerURL = '$WEINRE_PATH';
6
+ var prefixPath = window.__WHISTLE_PATH_PREFIX__;
7
+ if (/^\/[\w./-]+$/.test(prefixPath) && prefixPath.length <= 128) {
8
+ var len = prefixPath.length - 1;
9
+ if (prefixPath[len] === '/') {
10
+ prefixPath = prefixPath.substring(0, len);
11
+ }
12
+ } else {
13
+ prefixPath = '';
14
+ }
15
+ var baseUrl = '$BASE_URL' + prefixPath;
16
+ window.WeinreServerURL = baseUrl + '$WEINRE_PATH';
7
17
  var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
8
18
  var script = document.createElement('script');
9
19
  script.async = true;
10
20
  script.charset = 'utf8';
11
- script.src = '$WEINRE_URL';
21
+ script.src = baseUrl + '$WEINRE_URL';
12
22
  if (head.firstChild) {
13
23
  head.insertBefore(script, head.firstChild);
14
24
  } else {
@@ -22,6 +22,7 @@ var QUOTE_RE = /"/g;
22
22
  var JS_RE = /^js:/i;
23
23
  var HTML_RE = /^html:/i;
24
24
  var BLANK_RE = /\s/g;
25
+ var HASH_RE = /#.*$/;
25
26
 
26
27
  function urlToStr(url) {
27
28
  return url.replace(SLASH_RE, '').replace(QUOTE_RE, '\\$&').replace(BLANK_RE, ' ');
@@ -198,7 +199,11 @@ function handleLocHref(req, rule, handleRes) {
198
199
  }
199
200
  var type = isJs ? 'application/javascript' : 'text/html';
200
201
  if (body) {
201
- body = 'window.location.href = "' + urlToStr(body) + '";';
202
+ body = urlToStr(body);
203
+ if (util.compareUrl(body.replace(HASH_RE, ''), req.fullUrl)) {
204
+ return false;
205
+ }
206
+ body = 'window.location.href = "' + body + '";';
202
207
  if (!isJs) {
203
208
  body = '<script>' + body + '</script>';
204
209
  }
@@ -211,6 +216,7 @@ function handleLocHref(req, rule, handleRes) {
211
216
  'content-type': type + '; charset=utf-8'
212
217
  }
213
218
  });
219
+ return true;
214
220
  }
215
221
 
216
222
  module.exports = function (req, res, next) {
@@ -223,7 +229,12 @@ module.exports = function (req, res, next) {
223
229
  var rules = req.rules;
224
230
  var rule = rules.rule;
225
231
  if (protoMgr.isLocHref(protocol)) {
226
- return handleLocHref(req, rule, render);
232
+ if (handleLocHref(req, rule, render)) {
233
+ return;
234
+ }
235
+ req.isWebProtocol = true;
236
+ req.options = util.parseUrl(req.fullUrl);
237
+ return next();
227
238
  }
228
239
  if (isAutoCors(req, rule) && req.method === 'OPTIONS') {
229
240
  var optsRes = util.wrapResponse({ statusCode: 200 });
@@ -19,8 +19,8 @@ function getScript(host, isHtml, req) {
19
19
  var logCgiPath =
20
20
  config.WEBUI_PATH + 'log.' + config.port + '/cgi-bin/log/set';
21
21
  var result = isHtml ? logHtmlScript : logScript;
22
- logCgiPath = (req.isHttps ? 'https://' : 'http://') + host + logCgiPath;
23
- return result.replace('$LOG_CGI', logCgiPath);
22
+ var baseUrl = (req.isHttps ? 'https://' : 'http://') + host;
23
+ return result.replace('$BASE_URL', baseUrl).replace('$LOG_CGI', logCgiPath);
24
24
  }
25
25
 
26
26
  module.exports = function (req, res, next) {
@@ -1338,7 +1338,7 @@ module.exports = function (req, res, next) {
1338
1338
  });
1339
1339
  };
1340
1340
  var resHeaders = {};
1341
- var svRes = util.getStatusCodeFromRule(resRules);
1341
+ var svRes = util.getStatusCodeFromRule(resRules, req);
1342
1342
  if (svRes) {
1343
1343
  req.hostIp = LOCALHOST;
1344
1344
  resHeaders.Connection = 'close';
@@ -10,16 +10,12 @@ var weinreHtmlScript = '\r\n<script>' + weinreScript + '</script>\r\n';
10
10
 
11
11
  function getScript(host, name, isHtml, req) {
12
12
  host = util.getInternalHost(req, host);
13
- var weinrePath =
14
- (req.isHttps ? 'https://' : 'http://') +
15
- host +
16
- config.WEBUI_PATH +
17
- 'weinre.' +
18
- config.port;
13
+ var baseUrl = (req.isHttps ? 'https://' : 'http://') + host;
14
+ var weinrePath = config.WEBUI_PATH + 'weinre.' + config.port;
19
15
  var result = isHtml ? weinreHtmlScript : weinreScript;
20
16
  var weinreUrl =
21
17
  weinrePath + '/target/target-script-min.js#' + (name || 'anonymous');
22
- return result
18
+ return result.replace('$BASE_URL', baseUrl)
23
19
  .replace('$WEINRE_PATH', weinrePath)
24
20
  .replace('$WEINRE_URL', weinreUrl);
25
21
  }
@@ -245,17 +245,6 @@ function formatShorthand(url) {
245
245
  return url;
246
246
  }
247
247
 
248
- function formatUrl(pattern) {
249
- var queryString = '';
250
- var queryIndex = pattern.indexOf('?');
251
- if (queryIndex != -1) {
252
- queryString = pattern.substring(queryIndex);
253
- pattern = pattern.substring(0, queryIndex);
254
- }
255
- var index = pattern.indexOf('://');
256
- index = pattern.indexOf('/', index == -1 ? 0 : index + 3);
257
- return (index == -1 ? pattern + '/' : pattern) + queryString;
258
- }
259
248
 
260
249
  function getKey(url) {
261
250
  if (url.indexOf('{') == 0) {
@@ -354,7 +343,7 @@ function join(first, second) {
354
343
  }
355
344
  }
356
345
  var query = joinQuery(firstQuery, secondQuery);
357
- return WEB_PROTOCOL_RE.test(first) ? formatUrl(first + query) : first + query;
346
+ return WEB_PROTOCOL_RE.test(first) ? util.formatUrl(first + query) : first + query;
358
347
  }
359
348
 
360
349
  function toLine(_, line) {
@@ -844,7 +833,7 @@ function replaceSubMatcher(url, regExp, req) {
844
833
  }
845
834
 
846
835
  function resolveRuleList(req, list, vals, index, isFilter, isEnableProxy, host) {
847
- var curUrl = formatUrl(req.curUrl);
836
+ var curUrl = util.formatUrl(req.curUrl);
848
837
  var notHttp = list.isRuleProto && curUrl[0] !== 'h';
849
838
  //支持域名匹配
850
839
  var domainUrl = curUrl.replace(
@@ -1255,7 +1244,7 @@ function parseRule(rulesMgr, pattern, matcher, raw, root, options) {
1255
1244
  isRegExp: isRegExp,
1256
1245
  isExact: isExact,
1257
1246
  protocol: isRegExp ? null : util.getProtocol(pattern),
1258
- pattern: isRegExp ? pattern : formatUrl(pattern),
1247
+ pattern: isRegExp ? pattern : util.formatUrl(pattern),
1259
1248
  matcher: matcher,
1260
1249
  port: port,
1261
1250
  raw: raw,
@@ -2021,7 +2010,7 @@ proto.resolveHost = function (
2021
2010
  };
2022
2011
 
2023
2012
  proto.lookupHost = function (req, callback) {
2024
- req.curUrl = formatUrl(util.setProtocol(req.curUrl));
2013
+ req.curUrl = util.formatUrl(util.setProtocol(req.curUrl));
2025
2014
  var options = parseUrl(req.curUrl);
2026
2015
  lookup(
2027
2016
  options.hostname,
@@ -2060,7 +2049,7 @@ function getHostFromList(list, req, vals) {
2060
2049
  }
2061
2050
 
2062
2051
  proto.getHost = function (req, pluginRulesMgr, rulesFileMgr, headerRulesMgr) {
2063
- var curUrl = formatUrl(util.setProtocol(req.curUrl));
2052
+ var curUrl = util.formatUrl(util.setProtocol(req.curUrl));
2064
2053
  req.curUrl = curUrl;
2065
2054
  var filter = {};
2066
2055
  var filterHost =
@@ -254,11 +254,38 @@ function replaceProtocol(url, protocol) {
254
254
  return (protocol || 'http:') + removeProtocol(url);
255
255
  }
256
256
 
257
+ function formatUrl(pattern) {
258
+ var queryString = '';
259
+ var queryIndex = pattern.indexOf('?');
260
+ if (queryIndex != -1) {
261
+ queryString = pattern.substring(queryIndex);
262
+ pattern = pattern.substring(0, queryIndex);
263
+ }
264
+ var index = pattern.indexOf('://');
265
+ index = pattern.indexOf('/', index == -1 ? 0 : index + 3);
266
+ return (index == -1 ? pattern + '/' : pattern) + queryString;
267
+ }
268
+
257
269
  exports.hasProtocol = hasProtocol;
258
270
  exports.setProtocol = setProtocol;
259
271
  exports.getProtocol = getProtocol;
260
272
  exports.removeProtocol = removeProtocol;
261
273
  exports.replaceProtocol = replaceProtocol;
274
+ exports.formatUrl = formatUrl;
275
+
276
+ var QUERY_RE = /\/[^/]*(?:\?.*)?$/;
277
+ exports.getAbsUrl = function(url, fullUrl) {
278
+ if (HTTP_RE.test(url)) {
279
+ return formatUrl(url);
280
+ }
281
+ if (url[0] === '/') {
282
+ var index = fullUrl.indexOf('/', 8);
283
+ url = fullUrl.substring(0, index) + url;
284
+ return formatUrl(url);
285
+ }
286
+ url = fullUrl.replace(QUERY_RE, '') + '/' + url;
287
+ return formatUrl(url);
288
+ };
262
289
 
263
290
  function getHomedir() {
264
291
  //默认设置为`~`,防止Linux在开机启动时Node无法获取homedir
package/lib/util/index.js CHANGED
@@ -607,6 +607,8 @@ function isEnable(req, name) {
607
607
 
608
608
  exports.isEnable = isEnable;
609
609
 
610
+ exports.formatUrl = common.formatUrl;
611
+
610
612
  exports.isKeepClientId = function(req, proxyUrl) {
611
613
  if (isEnable(req, 'keepClientId')) {
612
614
  return true;
@@ -926,6 +928,18 @@ exports.encodeNonLatin1Char = function (str) {
926
928
 
927
929
  exports.encodeURIComponent = safeEncodeURIComponent;
928
930
 
931
+ function compareUrl(url, fullUrl) {
932
+ url = common.getAbsUrl(url, fullUrl);
933
+ if (url === fullUrl) {
934
+ return true;
935
+ }
936
+ try {
937
+ return url === decodeURIComponent(fullUrl);
938
+ } catch (e) {}
939
+ }
940
+
941
+ exports.compareUrl = compareUrl;
942
+
929
943
  function getPath(url, noProtocol) {
930
944
  if (url) {
931
945
  url = url.replace(SEARCH_RE, '');
@@ -3780,19 +3794,26 @@ exports.setClientCert = function (options, key, cert, isPfx, cacheKey) {
3780
3794
  }
3781
3795
  };
3782
3796
 
3783
- exports.getStatusCodeFromRule = function (rules) {
3797
+ exports.getStatusCodeFromRule = function (rules, req) {
3784
3798
  var rule = rules.rule;
3785
3799
  var isSpec = rule && rule.isSpec;
3786
3800
  if (!isSpec) {
3787
3801
  return;
3788
3802
  }
3789
- rule = getMatcherValue(rule) || 200;
3790
- var result = { statusCode: rule, headers: {} };
3791
- if (isSpec === 2) {
3803
+ rule = getMatcherValue(rule) || '';
3804
+ var isRedirect = isSpec === 2;
3805
+ if (req && isRedirect && rule && compareUrl(rule, req.fullUrl)) {
3806
+ req.isWebProtocol = true;
3807
+ req.options = parseUrl(req.fullUrl);
3808
+ return;
3809
+ }
3810
+ var code = rule || 200;
3811
+ var result = { statusCode: code, headers: {} };
3812
+ if (isRedirect) {
3792
3813
  result.statusCode = 302;
3793
3814
  result.headers.location = rule;
3794
3815
  } else {
3795
- handleStatusCode(rule, result.headers);
3816
+ handleStatusCode(code, result.headers);
3796
3817
  }
3797
3818
  return result;
3798
3819
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "whistle",
3
3
  "description": "HTTP, HTTP2, HTTPS, Websocket debugging proxy",
4
- "version": "2.9.76",
4
+ "version": "2.9.77",
5
5
  "dataDirname": ".whistle",
6
6
  "localUIHost": "local.whistlejs.com",
7
7
  "port": 8899,
@@ -53,7 +53,7 @@
53
53
  "pfork": "^0.6.2",
54
54
  "pipestream": "^0.7.3",
55
55
  "safe-buffer": "^5.1.2",
56
- "set-global-proxy": "^0.2.0",
56
+ "set-global-proxy": "^0.2.1",
57
57
  "sni": "1.0.0",
58
58
  "sockx": "^0.2.1",
59
59
  "starting": "^8.0.2",