whistle 2.9.83 → 2.9.85-beta

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.
@@ -291,54 +291,60 @@ app.use(function(req, res, next) {
291
291
  if (req.headers.host !== 'rootca.pro') {
292
292
  return next();
293
293
  }
294
- var type = 'crt';
295
- if (!req.path.indexOf('/cer')) {
296
- type = 'cer';
294
+ var type = 'cer';
295
+ if (!req.path.indexOf('/crt')) {
296
+ type = 'crt';
297
297
  } else if (!req.path.indexOf('/pem')) {
298
298
  type = 'pem';
299
299
  }
300
300
  res.download(getRootCAFile(), 'rootCA.' + type);
301
301
  });
302
302
 
303
- function checkAllowOrigin(req) {
304
- if (config.allowAllOrigin ||
305
- req.headers['sec-fetch-site'] === 'same-origin' ||
306
- ALLOW_CROSS_URLS.indexOf(req.path) !== -1) {
303
+ function isAllowHost(host) {
304
+ var list = config.allowOrigin;
305
+ if (list) {
306
+ for (var i = 0, len = list.length; i < len; i++) {
307
+ var h = list[i];
308
+ if (typeof h === 'string' ? host === h : h.test(host)) {
309
+ return true;
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ function checkInternalPath(req) {
316
+ if (config.allowAllOrigin || ALLOW_CROSS_URLS.indexOf(req.path) !== -1) {
307
317
  return true;
308
318
  }
309
319
  var host = req.headers['x-whistle-origin-host'];
310
- var list = config.allowOrigin;
311
- if (!host) {
312
- host = req.headers.origin;
313
- if (!host || typeof host !== 'string') {
314
- return true;
315
- }
316
- if (!list) {
317
- return false;
318
- }
319
- var index = host.indexOf('://');
320
- if (index !== -1) {
321
- host = host.substring(index + 3);
322
- }
323
- host = util.parseHost(host)[0] || '*';
320
+ return !host || isAllowHost(host);
321
+ }
322
+
323
+ function checkAllowOrigin(req) {
324
+ var host = req.headers.origin;
325
+ if (!host || typeof host !== 'string' ||
326
+ req.headers['sec-fetch-site'] === 'same-origin') {
327
+ return false;
328
+ }
329
+ if (config.allowAllOrigin) {
330
+ return true;
324
331
  }
325
- if (host === '*') {
332
+ if (!config.allowOrigin) {
326
333
  return false;
327
334
  }
328
- if (list) {
329
- for (var i = 0, len = list.length; i < len; i++) {
330
- var h = list[i];
331
- return typeof h === 'string' ? host === h : h.test(host);
332
- }
335
+ var index = host.indexOf('://');
336
+ if (index !== -1) {
337
+ host = host.substring(index + 3);
333
338
  }
334
- return false;
339
+ host = util.parseHost(host)[0];
340
+ return host && isAllowHost(host);
335
341
  }
336
342
 
337
343
  function cgiHandler(req, res) {
338
- if (UP_PATH_REGEXP.test(req.path) || !checkAllowOrigin(req)) {
344
+ if (UP_PATH_REGEXP.test(req.path) || !checkInternalPath(req)) {
339
345
  return res.status(403).end('Forbidden');
340
346
  }
341
- if (req.headers.origin) {
347
+ if (checkAllowOrigin(req)) {
342
348
  res.setHeader('access-control-allow-origin', req.headers.origin);
343
349
  res.setHeader('access-control-allow-credentials', true);
344
350
  }
@@ -31,6 +31,7 @@ var getSNIServer = ca.getSNIServer;
31
31
  var getHttp2Server = ca.getHttp2Server;
32
32
  var LOCALHOST = '127.0.0.1';
33
33
  var tunnelTmplData = new LRU({ max: 3000, maxAge: 30000 });
34
+ var uaCache = new LRU({ max: 1024 });
34
35
  var TIMEOUT = 12000;
35
36
  var CONN_TIMEOUT = 60000;
36
37
  var X_RE = /^x/;
@@ -55,6 +56,16 @@ function handleWebsocket(socket, clientIp, clientPort) {
55
56
  });
56
57
  }
57
58
 
59
+ function setCaptureUA(ua) {
60
+ if (ua && typeof ua === 'string' && ua.length < 1024) {
61
+ uaCache.set(ua, 1);
62
+ }
63
+ }
64
+
65
+ function isCaptureUA(ua) {
66
+ return ua && uaCache.get(ua);
67
+ }
68
+
58
69
  function getTransProto(str) {
59
70
  if (!str || typeof str !== 'string') {
60
71
  return;
@@ -938,6 +949,7 @@ function addReqInfo(req) {
938
949
  }
939
950
  if (!req.isHttpH2) {
940
951
  headers[config.HTTPS_FIELD] = 1;
952
+ setCaptureUA(req.headers['user-agent']);
941
953
  }
942
954
  }
943
955
 
@@ -1122,10 +1134,16 @@ module.exports = function (socket, next, isWebPort) {
1122
1134
  var headersStr;
1123
1135
  var enable = socket.enable || '';
1124
1136
  var disable = socket.disable || '';
1125
- var isEnable = function(p1, p2) {
1126
- if (p2) {
1127
- return (enable[p1] || enable[p2]) && !disable[p1] && !disable[p2];
1137
+ var isCaptureIp = function() {
1138
+ if (disable.captureIp || disable.captureIP) {
1139
+ return false;
1140
+ }
1141
+ if (enable.capture || enable.captureIp || enable.captureIP) {
1142
+ return true;
1128
1143
  }
1144
+ return isCaptureUA(socket.headers['user-agent']);
1145
+ };
1146
+ var isEnable = function(p1) {
1129
1147
  return enable[p1] && !disable[p1];
1130
1148
  };
1131
1149
  var destroy = function (err) {
@@ -1178,7 +1196,7 @@ module.exports = function (socket, next, isWebPort) {
1178
1196
  return isWebPort ? socket.destroy() : next(chunk);
1179
1197
  }
1180
1198
  if (isHttp) {
1181
- if (isEnable('forHttps') || isEnable('captureHttp')) {
1199
+ if (isEnable('forHttps') || disable.captureHttp) {
1182
1200
  next(chunk);
1183
1201
  } else {
1184
1202
  socket.resume();
@@ -1188,7 +1206,7 @@ module.exports = function (socket, next, isWebPort) {
1188
1206
  addClientInfo(socket, chunk, statusLine, clientIp, clientPort)
1189
1207
  );
1190
1208
  }
1191
- } else if (isEnable('forHttp') || isEnable('captureHttps')) {
1209
+ } else if (isEnable('forHttp') || disable.captureHttps) {
1192
1210
  return next(chunk);
1193
1211
  } else {
1194
1212
  var isHttpH2 = HTTP2_RE.test(headersStr);
@@ -1260,8 +1278,7 @@ module.exports = function (socket, next, isWebPort) {
1260
1278
  var servername = useSNI || socket.tunnelHostname;
1261
1279
  if (
1262
1280
  !servername ||
1263
- (useSNI ? disable.captureSNI : (disable.captureNoSNI ||
1264
- (net.isIP(servername) && !isEnable('captureIp', 'captureIP')))) ||
1281
+ (useSNI ? disable.captureSNI : (disable.captureNoSNI || (net.isIP(servername) && !isCaptureIp()))) ||
1265
1282
  (socket.useProxifier && !ca.existsCustomCert(servername))
1266
1283
  ) {
1267
1284
  return next(chunk);
package/lib/init.js CHANGED
@@ -192,15 +192,11 @@ module.exports = function (req, res, next) {
192
192
  delete headers[config.HTTPS_FIELD];
193
193
  delete headers[config.HTTPS_PROTO_HEADER];
194
194
  }
195
- if (headers['proxy-connection']) {
196
- headers.connection = headers['proxy-connection'];
197
- }
198
195
  var sniPlugin = headers[config.SNI_PLUGIN_HEADER];
199
196
  if (sniPlugin) {
200
197
  req.sniPlugin = sniPlugin;
201
198
  delete headers[config.SNI_PLUGIN_HEADER];
202
199
  }
203
- delete headers['proxy-connection'];
204
200
  if (!req.isHttps && HTTPS_RE.test(req.url)) {
205
201
  req.isHttps = true;
206
202
  }
@@ -216,6 +212,14 @@ module.exports = function (req, res, next) {
216
212
  }
217
213
  req.rawHeaders = [];
218
214
  delete headers[config.ALPN_PROTOCOL_HEADER];
215
+ delete headers.connection;
216
+ }
217
+ var conn = headers['proxy-connection'];
218
+ if (conn) {
219
+ if (headers.connection) {
220
+ headers.connection = conn;
221
+ }
222
+ delete headers['proxy-connection'];
219
223
  }
220
224
  res.response = function (_res) {
221
225
  if (req._hasRespond) {
@@ -5,9 +5,9 @@ var socketMgr = require('../socket-mgr');
5
5
  var config = require('../config');
6
6
 
7
7
  var MAX_BODY_SIZE = 360 * 1024;
8
- var MAX_SIZE = (config.strict ? 256 : 768) * 1024;
9
- var MAX_REQ_BODY_SIZE = (config.strict ? 256 : 1536) * 1024;
10
- var MAX_RES_BODY_SIZE = (config.strict ? 256 : 1536) * 1024;
8
+ var MAX_SIZE = (config.strict ? 256 : 1024) * 1024;
9
+ var MAX_REQ_BODY_SIZE = (config.strict ? 256 : 2048) * 1024;
10
+ var MAX_RES_BODY_SIZE = (config.strict ? 256 : 2048) * 1024;
11
11
  var BIG_DATA_SIZE = 1024 * 1024 * 10;
12
12
  var LOCALHOST = '127.0.0.1';
13
13
 
@@ -45,11 +45,6 @@ function checkBodySize(data, useBigData) {
45
45
  }
46
46
  }
47
47
 
48
- function isUnzipJs(r) {
49
- r = r.headers;
50
- return !r['content-encoding'] && util.getContentType(r) === 'JS';
51
- }
52
-
53
48
  function getEventName(proxy) {
54
49
  if (util.listenerCount(proxy, '_request')) {
55
50
  return '_request';
@@ -113,9 +108,7 @@ function emitDataEvents(req, res, proxy) {
113
108
 
114
109
  var requestTime;
115
110
  var endTime;
116
- var isStream;
117
111
  var updateEvent;
118
- var useReqStream;
119
112
  var useFrames;
120
113
  var enable = req.enable;
121
114
  var disable = req.disable;
@@ -174,13 +167,14 @@ function emitDataEvents(req, res, proxy) {
174
167
  reqBody = null;
175
168
  }
176
169
  reqData.size = 0;
170
+ var isUnzip = !getZipType(info);
177
171
  var write = stream.write;
178
172
  var end = stream.end;
179
173
  var MAX_REQ_BODY = useBigData
180
174
  ? BIG_DATA_SIZE
181
- : info.headers && info.headers['content-encoding']
182
- ? MAX_SIZE
183
- : MAX_REQ_BODY_SIZE;
175
+ : (isUnzip
176
+ ? MAX_REQ_BODY_SIZE
177
+ : MAX_SIZE);
184
178
  stream.write = function (chunk) {
185
179
  if (chunk) {
186
180
  if (reqBody || reqBody === null) {
@@ -188,7 +182,7 @@ function emitDataEvents(req, res, proxy) {
188
182
  reqBody = '';
189
183
  } else {
190
184
  reqBody = reqBody ? Buffer.concat([reqBody, chunk]) : chunk;
191
- if (useReqStream) {
185
+ if (isUnzip) {
192
186
  reqData.body = reqBody;
193
187
  }
194
188
  if (reqBody.length > MAX_REQ_BODY) {
@@ -229,14 +223,14 @@ function emitDataEvents(req, res, proxy) {
229
223
  info =
230
224
  info ||
231
225
  (res._needGunzip ? { statusCode: res.statusCode, headers: '' } : res);
232
- var useStream = isStream && !getZipType(info);
226
+ var isUnzip = !getZipType(info);
233
227
  var MAX_RES_BODY = useBigData
234
228
  ? BIG_DATA_SIZE
235
- : isUnzipJs(info)
229
+ : (isUnzip
236
230
  ? MAX_RES_BODY_SIZE
237
- : MAX_SIZE;
231
+ : MAX_SIZE);
238
232
  if (!cleared && util.hasBody(info, req) && checkType(info)) {
239
- if (info.headers['content-length'] > MAX_RES_BODY) {
233
+ if (info.headers['content-length'] > MAX_RES_BODY && !util.isEnable(req, 'captureStream')) {
240
234
  resBody = false;
241
235
  } else {
242
236
  resBody = null;
@@ -252,7 +246,7 @@ function emitDataEvents(req, res, proxy) {
252
246
  resBody = '';
253
247
  } else {
254
248
  resBody = resBody ? Buffer.concat([resBody, chunk]) : chunk;
255
- if (useStream) {
249
+ if (isUnzip) {
256
250
  resData.body = resBody;
257
251
  }
258
252
  if (resBody.length > MAX_RES_BODY) {
@@ -399,13 +393,7 @@ function emitDataEvents(req, res, proxy) {
399
393
  resData.statusMessage = _res.statusMessage;
400
394
  data.responseTime = Date.now();
401
395
  if (!requestTime && !data.requestTime) {
402
- isStream = true;
403
- data.isStream = true;
404
396
  updateEvent = eventName;
405
- useReqStream = !getZipType(req);
406
- }
407
- if (useReqStream && reqBody) {
408
- reqData.body = reqBody;
409
397
  }
410
398
  resData.headers = _res.headers;
411
399
  }
@@ -392,7 +392,7 @@ module.exports = function (req, res, next) {
392
392
  } else if (!options.port) {
393
393
  options.port = isHttps ? 443 : 80;
394
394
  }
395
- proxyHeaders.host = util.join(options.host, options.port);
395
+ proxyHeaders.host = util.joinIpPort(options.host, options.port);
396
396
  } else {
397
397
  options.host = options.hostname;
398
398
  }
@@ -917,7 +917,7 @@ module.exports = function (req, res, next) {
917
917
  newType[0] =
918
918
  !type || type.indexOf('/') != -1
919
919
  ? type
920
- : mime.lookup(type, type);
920
+ : (type === 'sse' ? 'text/event-stream' : mime.lookup(type, type));
921
921
  util.setHeader(data, 'content-type', newType.join(';'));
922
922
  }
923
923
 
@@ -206,7 +206,7 @@ pluginMgr.on('updateRules', function (byParse) {
206
206
  });
207
207
  }
208
208
  if (rules) {
209
- const root = plugin.path;
209
+ var root = plugin.path;
210
210
  var index = 0;
211
211
  rules = rules.replace(REMOTE_RULES_RE, function (_, apo, rulesUrl) {
212
212
  if (index >= MAX_REMOTE_RULES_COUNT) {
@@ -162,6 +162,13 @@ function getIndex(startTime, start, end) {
162
162
  return getIndex(startTime, start, midIndex);
163
163
  }
164
164
 
165
+ function toBase64String(data) {
166
+ if (Buffer.isBuffer(data.body)) {
167
+ data.base64 = data.body.toString('base64');
168
+ data.body = '';
169
+ }
170
+ }
171
+
165
172
  function getIds(startTime, count, lastRowId) {
166
173
  startTime = startTime || lastRowId;
167
174
  if (!startTime) {
@@ -184,12 +191,77 @@ function getIds(startTime, count, lastRowId) {
184
191
  return ids.slice(index, index + count);
185
192
  }
186
193
 
187
- function getList(ids) {
194
+ function removeBase64(data) {
195
+ var result = {};
196
+ Object.keys(data).forEach(function(key) {
197
+ if (key !== 'headers' && key !== 'base64' && key !== 'rawHeaderNames') {
198
+ result[key] = data[key];
199
+ }
200
+ });
201
+ return result;
202
+ }
203
+
204
+ function getBase64Len(base64) {
205
+ if (!base64) {
206
+ return 0;
207
+ }
208
+ var len = base64.length;
209
+ if (base64[len - 1] === '=') {
210
+ len -= 2;
211
+ if (base64[len] === '=') {
212
+ --len;
213
+ }
214
+ }
215
+ return len;
216
+ }
217
+
218
+ function setBody(item, len) {
219
+ if (!(len > 0)) {
220
+ return item;
221
+ }
222
+ var base64 = item.base64;
223
+ var curLen = getBase64Len(base64);
224
+ item = removeBase64(item);
225
+ if (len < curLen) {
226
+ item.preLen = len;
227
+ item.base64 = base64.substring(len);
228
+ }
229
+ return item;
230
+ }
231
+
232
+ function getList(ids, status) {
188
233
  if (!Array.isArray(ids)) {
189
234
  return [];
190
235
  }
191
236
  return ids.map(function (id, i) {
192
- return reqData[id];
237
+ var data = reqData[id];
238
+ var opts = status && status[i];
239
+ if (data) {
240
+ toBase64String(data.req);
241
+ toBase64String(data.res);
242
+ }
243
+ if (!data || typeof opts !== 'string') {
244
+ return data;
245
+ }
246
+ opts = opts.split('-');
247
+ var result = {};
248
+ Object.keys(data).forEach(function(key) {
249
+ if (key === 'url' || key === 'rulesHeaders' || (key === 'rules' && opts[1] != '0')) {
250
+ return;
251
+ }
252
+ var item = data[key];
253
+ if (key === 'req') {
254
+ if (!opts[0]) {
255
+ item = removeBase64(item);
256
+ } else {
257
+ item = setBody(item, +opts[0]);
258
+ }
259
+ } else if (key === 'res') {
260
+ item = setBody(item, +opts[1]);
261
+ }
262
+ result[key] = item;
263
+ });
264
+ return result;
193
265
  });
194
266
  }
195
267
 
@@ -498,13 +570,6 @@ module.exports = function init(_proxy) {
498
570
  return result;
499
571
  };
500
572
 
501
- function toBase64String(data) {
502
- if (Buffer.isBuffer(data.body)) {
503
- data.base64 = data.body.toString('base64');
504
- data.body = '';
505
- }
506
- }
507
-
508
573
  proxy.getItem = function (id) {
509
574
  var item = reqData[id];
510
575
  if (item) {
@@ -557,10 +622,6 @@ module.exports = function init(_proxy) {
557
622
  : getIds(startTime, count, options.lastRowId);
558
623
  var setData = function (item) {
559
624
  if (item) {
560
- var req = item.req;
561
- var res = item.res;
562
- toBase64String(req);
563
- toBase64String(res);
564
625
  if (config.secureFilter) {
565
626
  try {
566
627
  item = config.secureFilter(item, clientIp, filter) || item;
@@ -583,6 +644,8 @@ module.exports = function init(_proxy) {
583
644
  while (id && count > 0) {
584
645
  var item = reqData[id];
585
646
  if (checkItem(item, filter)) {
647
+ toBase64String(item.req);
648
+ toBase64String(item.res);
586
649
  setData(item);
587
650
  newIds.push(id);
588
651
  --count;
@@ -593,7 +656,7 @@ module.exports = function init(_proxy) {
593
656
  getList(newIds).forEach(setData);
594
657
  }
595
658
  }
596
- getList(options.ids).forEach(setData);
659
+ getList(options.ids, options.status).forEach(setData);
597
660
  var endId = ids[ids.length - 1];
598
661
  var lastFrameId, frames;
599
662
  if (options.lastFrameId == -3) {
package/lib/util/index.js CHANGED
@@ -3165,7 +3165,7 @@ exports.disableReqProps = function (req) {
3165
3165
  delete headers['user-agent'];
3166
3166
  }
3167
3167
 
3168
- if (disable.gzip) {
3168
+ if (disable.gzip || isEnable(req, 'captureStream')) {
3169
3169
  delete headers['accept-encoding'];
3170
3170
  }
3171
3171
 
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.83",
4
+ "version": "2.9.85-beta",
5
5
  "dataDirname": ".whistle",
6
6
  "localUIHost": "local.whistlejs.com",
7
7
  "port": 8899,
@@ -57,7 +57,7 @@
57
57
  "sni": "1.0.0",
58
58
  "sockx": "^0.2.2",
59
59
  "starting": "^8.0.3",
60
- "weinre2": "^1.3.4",
60
+ "weinre2": "^1.3.6",
61
61
  "ws-parser": "^0.6.4",
62
62
  "xml2js": "0.5.0"
63
63
  },
Binary file