whistle 2.10.2 → 2.10.4

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 (58) hide show
  1. package/assets/menu.html +2 -0
  2. package/assets/modal.html +2 -0
  3. package/assets/tab.html +3 -3
  4. package/bin/proxy.js +2 -2
  5. package/bin/whistle.js +5 -5
  6. package/biz/webui/cgi-bin/add-rules-values.js +1 -1
  7. package/biz/webui/cgi-bin/check-update.js +0 -1
  8. package/biz/webui/cgi-bin/do-not-show-again.js +1 -1
  9. package/biz/webui/cgi-bin/download.js +1 -1
  10. package/biz/webui/cgi-bin/enable-http2.js +1 -1
  11. package/biz/webui/cgi-bin/get-matched-rules.js +14 -0
  12. package/biz/webui/cgi-bin/hide-https-connects.js +1 -1
  13. package/biz/webui/cgi-bin/intercept-https-connects.js +1 -1
  14. package/biz/webui/cgi-bin/plugins/disable-all-plugins.js +1 -1
  15. package/biz/webui/cgi-bin/reset-local-address.js +1 -1
  16. package/biz/webui/cgi-bin/rules/allow-multiple-choice.js +1 -1
  17. package/biz/webui/cgi-bin/rules/clear-dns-cache.js +7 -0
  18. package/biz/webui/cgi-bin/rules/disable-all-rules.js +1 -1
  19. package/biz/webui/cgi-bin/rules/disable-default.js +1 -1
  20. package/biz/webui/cgi-bin/rules/enable-back-rules-first.js +1 -1
  21. package/biz/webui/cgi-bin/rules/enable-default.js +1 -1
  22. package/biz/webui/cgi-bin/rules/move-to.js +1 -1
  23. package/biz/webui/cgi-bin/rules/remove.js +1 -1
  24. package/biz/webui/cgi-bin/rules/rename.js +1 -1
  25. package/biz/webui/cgi-bin/rules/select.js +1 -1
  26. package/biz/webui/cgi-bin/rules/set-sys-hosts.js +1 -1
  27. package/biz/webui/cgi-bin/rules/unselect.js +1 -1
  28. package/biz/webui/cgi-bin/values/move-to.js +1 -1
  29. package/biz/webui/cgi-bin/values/remove.js +1 -1
  30. package/biz/webui/cgi-bin/values/rename.js +1 -1
  31. package/biz/webui/htdocs/index.html +1 -1
  32. package/biz/webui/htdocs/js/index.js +54 -52
  33. package/biz/webui/lib/index.js +32 -2
  34. package/lib/config.js +4 -4
  35. package/lib/handlers/file-proxy.js +1 -1
  36. package/lib/https/ca.js +31 -0
  37. package/lib/https/index.js +4 -2
  38. package/lib/index.js +0 -2
  39. package/lib/inspectors/data.js +120 -13
  40. package/lib/inspectors/req.js +5 -3
  41. package/lib/plugins/get-plugins-sync.js +15 -11
  42. package/lib/plugins/get-plugins.js +11 -12
  43. package/lib/plugins/load-plugin.js +1 -1
  44. package/lib/plugins/module-paths.js +4 -0
  45. package/lib/plugins/util.js +8 -0
  46. package/lib/rules/dns.js +4 -0
  47. package/lib/rules/rules.js +7 -8
  48. package/lib/service/composer.js +86 -39
  49. package/lib/service/service.js +14 -10
  50. package/lib/service/util.js +3 -42
  51. package/lib/socket-mgr.js +2 -16
  52. package/lib/tunnel.js +3 -0
  53. package/lib/upgrade.js +1 -0
  54. package/lib/util/common.js +54 -10
  55. package/lib/util/data-server.js +10 -0
  56. package/lib/util/index.js +38 -19
  57. package/package.json +2 -2
  58. package/biz/webui/cgi-bin/import-remote.js +0 -37
@@ -7,6 +7,7 @@ var rulesUtil = require('./util');
7
7
  var lookup = require('./dns');
8
8
  var protoMgr = require('./protocols');
9
9
  var config = require('../config');
10
+ var common = require('../util/common');
10
11
 
11
12
  var rules = rulesUtil.rules;
12
13
  var values = rulesUtil.values;
@@ -14,7 +15,6 @@ var env = process.env || {};
14
15
  var allowDnsCache = true;
15
16
  var SUB_MATCH_RE = /\$[&\d]/;
16
17
  var BODY_MATCH_RE = /\$b[&\d]/;
17
- var SPACE_RE = /\s+/g;
18
18
  var EXACT_RE = /^\$/;
19
19
  var NON_RE = /^!/;
20
20
  var HAS_SPACE_RE = /\s/;
@@ -69,7 +69,6 @@ var NON_STAR_RE = /[^*]/;
69
69
  var DOMAIN_STAR_RE = /([*~]+)(\\.)?/g;
70
70
  var STAR_RE = /\*+/g;
71
71
  var PORT_PATTERN_RE = /^!?:\d{1,5}$/;
72
- var COMMENT_RE = /#[^\r\n]*/g;
73
72
  var TPL_RE = /^((?:[\w.-]+:)?\/\/)?(`.*`)$/;
74
73
  // url: protocol, host, port, hostname, search, query, pathname, path, href, query.key
75
74
  // req|res: ip, method, statusCode, headers?.key, cookies?.key
@@ -102,6 +101,7 @@ var TOOL_RE = /^(?:log|weinre):\/\//;
102
101
  var mIndex = 0;
103
102
  var mNow = Date.now();
104
103
  var IS_JSON = Symbol('isJson');
104
+ var removeComment = common.removeComment;
105
105
 
106
106
  function getMFlag() {
107
107
  if (mIndex === Number.MAX_SAFE_INTEGER) {
@@ -111,10 +111,6 @@ function getMFlag() {
111
111
  return mNow + '-' + (mIndex++);
112
112
  }
113
113
 
114
- function removeComment(text) {
115
- return text.replace(COMMENT_RE, '').trim();
116
- }
117
-
118
114
  function domainToRegExp(all, star, dot) {
119
115
  var len = star.length;
120
116
  var result = len > 1 ? '([^/?]*)' : '([^/?.]*)';
@@ -371,7 +367,7 @@ function joinUrl(a, b) {
371
367
  }
372
368
 
373
369
  function toLine(_, line) {
374
- return line.replace(SPACE_RE, ' ');
370
+ return line.replace(/\s+/g, ' ');
375
371
  }
376
372
 
377
373
  function mergeLines(text) {
@@ -1839,6 +1835,9 @@ function matchFilter(url, filter, req) {
1839
1835
  if (filter.from === 'tunnel') {
1840
1836
  return filter.not ? !req.fromTunnel : req.fromTunnel;
1841
1837
  }
1838
+ if (filter.from === 'test') {
1839
+ return filter.not ? !req._testId : req._testId;
1840
+ }
1842
1841
  if (filter.from === 'composer') {
1843
1842
  return filter.not ? !req.fromComposer : req.fromComposer;
1844
1843
  }
@@ -1866,7 +1865,7 @@ function matchFilter(url, filter, req) {
1866
1865
  if (keyLen > 0 && key[keyLen] === '%') {
1867
1866
  key = key.substring(0, keyLen) / 100;
1868
1867
  }
1869
- return getFilterResult(Math.random() < chance.key, filter);
1868
+ return getFilterResult(Math.random() < key, filter);
1870
1869
  }
1871
1870
  if (filterProp(req.method, filter.method, filter.mPattern)) {
1872
1871
  return getFilterResult(result, filter);
@@ -29,6 +29,7 @@ var MAX_BODY_LEN = 260 * 1024;
29
29
  var MAX_BASE64_LEN = 360 * 1024;
30
30
  var MAX_METHOD_LEN = 64;
31
31
  var LEN_HEADER = 'content-length';
32
+ var testIndex = 0;
32
33
 
33
34
  var composerTimer;
34
35
  function saveComposerHistory() {
@@ -38,7 +39,12 @@ function saveComposerHistory() {
38
39
  } catch (e) {}
39
40
  }
40
41
 
41
- function handleComposerHistory(data) {
42
+ function getUrl(url, isConn) {
43
+ url = isConn ? url.replace(/[?#].*$/, '') : url;
44
+ return url.length > MAX_URL_LEN ? url.substring(0, MAX_URL_LEN) : url;
45
+ }
46
+
47
+ function handleComposerHistory(data, isConn) {
42
48
  var url = data.url;
43
49
  var method = data.method;
44
50
  var headers = data.headers;
@@ -50,7 +56,7 @@ function handleComposerHistory(data) {
50
56
  var result = {
51
57
  date: Date.now(),
52
58
  useH2: data.useH2,
53
- url: url.length > MAX_URL_LEN ? url.substring(0, MAX_URL_LEN) : url,
59
+ url: getUrl(url, isConn),
54
60
  method:
55
61
  method.length > MAX_METHOD_LEN
56
62
  ? method.substring(0, MAX_METHOD_LEN)
@@ -120,7 +126,24 @@ function getReqCount(count) {
120
126
  return count > 0 ? Math.min(count, MAX_REQ_COUNT) : 1;
121
127
  }
122
128
 
123
- function handleConnect(options, cb, count) {
129
+ function getResponse(res, testId) {
130
+ if (testId) {
131
+ return { testId: testId };
132
+ }
133
+ return { statusCode: res.statusCode, headers: res.headers || {} };
134
+ }
135
+
136
+ function handleCallback(cb, err, res, testId) {
137
+ if (!cb) {
138
+ return;
139
+ }
140
+ if (testId) {
141
+ return cb(null, getResponse(null, testId));
142
+ }
143
+ cb(err, !err && getResponse(res));
144
+ }
145
+
146
+ function handleConnect(options, cb, count, testId) {
124
147
  count = getReqCount(count);
125
148
  options.headers['x-whistle-policy'] = 'tunnel';
126
149
  var origOpts = options;
@@ -140,7 +163,7 @@ function handleConnect(options, cb, count) {
140
163
  headers: options.headers
141
164
  }, function(socket, svrRes, err) {
142
165
  if (err) {
143
- return execCb && execCb(err);
166
+ return handleCallback(execCb, err, null, testId);
144
167
  }
145
168
  if (TLS_PROTOS.indexOf(options.protocol) !== -1) {
146
169
  socket = tls.connect({
@@ -155,10 +178,7 @@ function handleConnect(options, cb, count) {
155
178
  socket.write(data);
156
179
  options.body = data = null;
157
180
  }
158
- execCb && execCb(null, {
159
- statusCode: svrRes.statusCode,
160
- headers: svrRes.headers
161
- });
181
+ handleCallback(execCb, err, svrRes, testId);
162
182
  }, config).on('error', execCb || noop);
163
183
  }
164
184
  }
@@ -173,7 +193,7 @@ function getReqRaw(options) {
173
193
  return raw + '\r\n\r\n';
174
194
  }
175
195
 
176
- function handleWebSocket(options, cb, count) {
196
+ function handleWebSocket(options, cb, count, testId) {
177
197
  count = getReqCount(count);
178
198
  if (options.protocol === 'https:' || options.protocol === 'wss:') {
179
199
  options.headers[common.HTTPS_FIELD] = 1;
@@ -191,7 +211,7 @@ function handleWebSocket(options, cb, count) {
191
211
  }
192
212
  common.connect(PROXY_OPTS, function(err, socket) {
193
213
  if (err) {
194
- execCb && execCb(err);
214
+ handleCallback(execCb, err, null, testId);
195
215
  } else {
196
216
  socket.write(getReqRaw(options));
197
217
  var data = options.body;
@@ -201,7 +221,7 @@ function handleWebSocket(options, cb, count) {
201
221
  parseReq(socket, function(e) {
202
222
  if (e) {
203
223
  socket.destroy();
204
- return execCb && execCb(e);
224
+ return handleCallback(execCb, e, null, testId);
205
225
  }
206
226
  var statusCode = socket.statusCode;
207
227
  if (statusCode == 101) {
@@ -221,17 +241,14 @@ function handleWebSocket(options, cb, count) {
221
241
  } else {
222
242
  socket.destroy();
223
243
  }
224
- execCb && execCb(null, {
225
- statusCode: statusCode,
226
- headers: socket.headers || {}
227
- });
244
+ handleCallback(execCb, err, socket, testId);
228
245
  }, true);
229
246
  }
230
247
  });
231
248
  }
232
249
  }
233
250
 
234
- function handleHttp(options, cb, count, reqId) {
251
+ function handleHttp(options, cb, count, reqId, testId) {
235
252
  count = getReqCount(count);
236
253
  if (options.protocol === 'https:') {
237
254
  options.headers[common.HTTPS_FIELD] = 1;
@@ -250,7 +267,8 @@ function handleHttp(options, cb, count, reqId) {
250
267
  options = extend({}, origOpts);
251
268
  }
252
269
  var client = http.request(options, function(svrRes) {
253
- if (!execCb) {
270
+ if (!execCb || testId) {
271
+ handleCallback(execCb, null, svrRes, testId);
254
272
  return drain(svrRes);
255
273
  }
256
274
  svrRes.on('error', execCb);
@@ -333,15 +351,16 @@ function getCharset(headers) {
333
351
  }
334
352
 
335
353
  exports.handleRequest = function(req, res) {
336
- var fullUrl = req.body.url;
354
+ var reqBody = req.body;
355
+ var fullUrl = reqBody.url;
337
356
  if (!fullUrl || typeof fullUrl !== 'string') {
338
- return res.json({ec: 0});
357
+ return res.json({ec: 2, em: 'Invalid URL'});
339
358
  }
340
359
 
341
360
  fullUrl = common.encodeNonLatin1Char(fullUrl.replace(/#.*$/, ''));
342
361
  var options = common.parseUrl(common.setProtocol(fullUrl));
343
362
  if (!options.host) {
344
- return res.json({ec: 0});
363
+ return res.json({ec: 2, em: 'Invalid URL'});
345
364
  }
346
365
  var protocol = options.protocol;
347
366
  if (protocol) {
@@ -349,13 +368,41 @@ exports.handleRequest = function(req, res) {
349
368
  }
350
369
  var rawHeaderNames = {};
351
370
  var clientId = req.headers[config.CLIENT_ID_HEADER];
352
- var headers = parseHeaders(req.body.headers, rawHeaderNames, clientId);
353
- var method = common.getMethod(req.body.method);
371
+ var headers = parseHeaders(reqBody.headers, rawHeaderNames, clientId);
372
+ var method = common.getMethod(reqBody.method);
354
373
  var isWebSocket = method === 'WEBSOCKET';
374
+ var rules = common.trimStr(reqBody.rules);
375
+ if (rules) {
376
+ var curRules = headers['x-whistle-rule-value'];
377
+ if (Array.isArray(curRules)) {
378
+ curRules = curRules.join('\n');
379
+ }
380
+ curRules = common.trimStr(curRules);
381
+ if (curRules) {
382
+ try {
383
+ curRules = decodeURIComponent(curRules);
384
+ rules = common.removeComment(rules + '\n' + curRules);
385
+ } catch (e) {}
386
+ }
387
+ headers['x-whistle-rule-value'] = common.safeEncodeURIComponent(rules);
388
+ }
389
+ var type = common.trimStr(reqBody.contentType);
390
+ if (type) {
391
+ headers['content-type'] = type;
392
+ }
393
+ if (reqBody.contentLength >= 0) {
394
+ headers['content-length'] = reqBody.contentLength;
395
+ }
355
396
  delete headers[config.WEBUI_HEAD];
356
397
  headers[config.FROM_COM_HEADER] = '1';
357
- if (req.body.enableProxyRules === false) {
398
+ var isTest = reqBody.isTest;
399
+ var testId;
400
+ if (reqBody.enableProxyRules === false || isTest) {
358
401
  headers[config.DISABLE_RULES_HEADER] = '1';
402
+ if (isTest) {
403
+ testId = ++testIndex + '';
404
+ headers[config.TEST_HEADER] = testId;
405
+ }
359
406
  }
360
407
  headers.host = options.host;
361
408
  options.clientId = clientId;
@@ -365,8 +412,8 @@ exports.handleRequest = function(req, res) {
365
412
 
366
413
  var isConn = common.isConnect(options);
367
414
  var isWs = !isConn && (isWebSocket || common.isUpgrade(options, headers));
368
- var useH2 = req.body.useH2 || req.body.isH2;
369
- req.body.useH2 = false;
415
+ var useH2 = reqBody.useH2 || reqBody.isH2;
416
+ reqBody.useH2 = false;
370
417
  if (isWs) {
371
418
  headers.connection = 'Upgrade';
372
419
  headers.upgrade = (!isWebSocket && headers.upgrade) || 'websocket';
@@ -377,7 +424,7 @@ exports.handleRequest = function(req, res) {
377
424
  } else {
378
425
  delete headers.upgrade;
379
426
  if (!isConn && ((useH2 && (protocol === 'https:' || protocol === 'http:')) || protocol === 'h2:' || protocol === 'http2:')) {
380
- req.body.useH2 = true;
427
+ reqBody.useH2 = true;
381
428
  var isHttp = protocol === 'http:';
382
429
  options.protocol = isHttp ? 'http:' : 'https:';
383
430
  if (!headers[config.ALPN_PROTOCOL_HEADER]) {
@@ -385,8 +432,8 @@ exports.handleRequest = function(req, res) {
385
432
  }
386
433
  }
387
434
  }
388
- if (!req.body.noStore && req.body.needResponse && common.checkHistory(req.body)) {
389
- handleComposerHistory(req.body);
435
+ if (!reqBody.noStore && !testId && reqBody.needResponse && common.checkHistory(reqBody)) {
436
+ handleComposerHistory(reqBody, isConn);
390
437
  dataCenter.saveData({
391
438
  type: 'composer',
392
439
  history: composerHistory
@@ -394,15 +441,15 @@ exports.handleRequest = function(req, res) {
394
441
  }
395
442
 
396
443
  var getBody = function(cb) {
397
- var base64 = req.body.base64;
398
- var body = base64 || req.body.body;
444
+ var base64 = reqBody.base64;
445
+ var body = base64 || reqBody.body;
399
446
  if (!isWs) {
400
447
  delete headers.trailer;
401
448
  }
402
449
  if (isWs || isConn || common.hasRequestBody(options)) {
403
450
  body = body && common.toBuffer(body, base64 ? 'base64' : getCharset(headers));
404
451
  options.body = body;
405
- if (!isWs && !isConn && body && req.body.isGzip) {
452
+ if (!isWs && !isConn && body && reqBody.isGzip) {
406
453
  gzip(body, function(err, gzipData) {
407
454
  if (err) {
408
455
  return cb(err);
@@ -434,7 +481,7 @@ exports.handleRequest = function(req, res) {
434
481
  getBody(function(err) {
435
482
  options.headers = formatHeaders(headers, rawHeaderNames);
436
483
  var done;
437
- var needResponse = req.query.needResponse || req.body.needResponse;
484
+ var needResponse = req.query.needResponse || reqBody.needResponse;
438
485
  var handleResponse = needResponse ? function(err, data) {
439
486
  if (done) {
440
487
  return;
@@ -448,23 +495,23 @@ exports.handleRequest = function(req, res) {
448
495
  }});
449
496
  return;
450
497
  }
451
- common.sendGzip(req, res, {ec: 0, em: 'success', res: data || ''});
498
+ common.sendGzip(req, res, {ec: 0, res: data || ''});
452
499
  } : null;
453
500
  if (err) {
454
501
  return handleResponse && handleResponse(err);
455
502
  }
456
- var count = req.body.repeatCount;
457
- count = count > 0 ? count : req.body.repeatTimes;
503
+ var count = reqBody.repeatCount;
504
+ count = count > 0 ? count : reqBody.repeatTimes;
458
505
  if (isWs) {
459
506
  options.method = 'GET';
460
- handleWebSocket(options, handleResponse, count);
507
+ handleWebSocket(options, handleResponse, count, testId);
461
508
  } else if (isConn) {
462
- handleConnect(options, handleResponse, count);
509
+ handleConnect(options, handleResponse, count, testId);
463
510
  } else {
464
- handleHttp(options, handleResponse, count, req.body.reqId);
511
+ handleHttp(options, handleResponse, count, reqBody.reqId, testId);
465
512
  }
466
513
  if (!handleResponse) {
467
- res.json({ec: 0, em: 'success'});
514
+ res.json({ec: 0});
468
515
  }
469
516
  });
470
517
  };
@@ -32,7 +32,7 @@ var TEMP_PLUGINS_PATH = path.join(common.getWhistlePath(), '.temp_plugins');
32
32
  var SESSIONS_FILE_RE = /\.(txt|json|saz)$/i;
33
33
  var limiter = new Limiter({ concurrency: 10 });
34
34
  var MAX_PLUGINS = 10;
35
- var TEMP_FILES_PATH;
35
+ var TEMP_FILES_PATH = common.TEMP_FILES_PATH;
36
36
  var SAVED_SESSIONS_PATH;
37
37
  var LIMIT_SIZE = 1024 * 1024 * 128;
38
38
  var MAX_TEMP_SIZE = 1024 * 1024 * 12;
@@ -47,7 +47,6 @@ var hasWhistleToken;
47
47
  var whistleIdFile;
48
48
  var storage = multer.memoryStorage();
49
49
  var INVALID_NAME_RE = /[\u001e\u001f\u200e\u200f\u200d\u200c\u202a\u202d\u202e\u202c\u206e\u206f\u206b\u206a\u206d\u206c'<>:"\\/|?*]+/g;
50
- var SPACE_RE = /\s+/g;
51
50
  var upload = multer({
52
51
  storage: storage,
53
52
  fieldSize: LIMIT_SIZE
@@ -91,7 +90,7 @@ process.on('data', function(data) {
91
90
  });
92
91
 
93
92
  function getFilename(filename, time, count) {
94
- filename = common.isString(filename) ? filename.replace(INVALID_NAME_RE, '').replace(SPACE_RE, ' ').substring(0, 64).trim() : '';
93
+ filename = common.isString(filename) ? filename.replace(INVALID_NAME_RE, '').replace(/\s+/g, ' ').substring(0, 64).trim() : '';
95
94
  time = time || Date.now();
96
95
  return common.getMonth(time) + '/' + filename + '_' + count + '_' + time;
97
96
  }
@@ -108,7 +107,7 @@ function saveSessions(data, cb) {
108
107
  return cb(err);
109
108
  }
110
109
  var filename = getFilename(data.filename, 0, count);
111
- util.writeFile(path.join(SAVED_SESSIONS_PATH, filename), buf, function(e) {
110
+ common.writeFile(path.join(SAVED_SESSIONS_PATH, filename), buf, function(e) {
112
111
  !e && saveData({ type: 'savedData', filename: filename, buffer: buf } );
113
112
  cb(e);
114
113
  });
@@ -167,7 +166,8 @@ function getTempFiles(list, cb) {
167
166
  }
168
167
  };
169
168
  list = list.forEach(function(filename) {
170
- if (!common.isTempFile(filename)) {
169
+ filename = common.getTempFile(filename);
170
+ if (!filename) {
171
171
  return execCb();
172
172
  }
173
173
  filename = path.join(TEMP_FILES_PATH, filename);
@@ -194,7 +194,7 @@ function writeTempPlugin(plugin, data, cb) {
194
194
  plugin = plugin.split('/').pop();
195
195
  var tempPath = path.join(TEMP_PLUGINS_PATH, plugin);
196
196
  ensureTempPluginsDir();
197
- util.writeFile(tempPath, data, function(e) {
197
+ common.writeFile(tempPath, data, function(e) {
198
198
  cb(e, e ? null : tempPath);
199
199
  });
200
200
  }
@@ -308,6 +308,10 @@ function sessionsHandler() {
308
308
  function abort() {
309
309
  res.destroy();
310
310
  }
311
+ if (req.headers[common.WHISTLE_UID_HEADER] !== config.uid) {
312
+ return res.status(403).end('Forbidden');
313
+ }
314
+ delete req.headers[common.WHISTLE_UID_HEADER];
311
315
  next();
312
316
  });
313
317
  app.get('/service/*', forwardRequest);
@@ -456,8 +460,9 @@ function sessionsHandler() {
456
460
  });
457
461
  }
458
462
  var filename = req.query.filename;
459
- if (common.isTempFile(filename)) {
460
- filename = path.join(TEMP_FILES_PATH, filename);
463
+ var tempFile = common.getTempFile(filename);
464
+ if (tempFile) {
465
+ filename = path.join(TEMP_FILES_PATH, tempFile);
461
466
  }
462
467
  getFile(filename, function(em, data) {
463
468
  if (em) {
@@ -470,7 +475,7 @@ function sessionsHandler() {
470
475
  app.use('/cgi-bin/temp/create',
471
476
  jsonParser,
472
477
  function(req, res) {
473
- util.writeTempFile(TEMP_FILES_PATH, getContent(req.body), function(e, filename) {
478
+ common.writeTempFile(getContent(req.body), function(e, filename) {
474
479
  if (e) {
475
480
  return res.json({ ec: 2, em: e.message });
476
481
  }
@@ -609,7 +614,6 @@ function checkWhistleId(cb) {
609
614
  module.exports = function (options, callback) {
610
615
  config = options;
611
616
  whistleIdFile = path.join(options.baseDir, options.storage || '', '.whistle_id');
612
- TEMP_FILES_PATH = options.TEMP_FILES_PATH;
613
617
  SAVED_SESSIONS_PATH = options.SAVED_SESSIONS_PATH;
614
618
  dataCenter.setup(options);
615
619
  composer.setup(options);
@@ -1,7 +1,5 @@
1
- var crypto = require('crypto');
2
1
  var path = require('path');
3
2
  var fs = require('fs');
4
- var fse = require('fs-extra2');
5
3
  var zlib = require('../util/zlib');
6
4
  var common = require('../util/common');
7
5
 
@@ -426,16 +424,6 @@ function parseFrames(res, content, callback) {
426
424
 
427
425
  exports.parseFrames = parseFrames;
428
426
 
429
- function getHexHash(ctn, key) {
430
- return crypto
431
- .createHmac('sha256', key || 'whistle_temp_files')
432
- .update(ctn || '').digest('hex').toLowerCase();
433
- }
434
-
435
- function descSorter(a, b) {
436
- return a > b ? -1 : 1;
437
- }
438
-
439
427
  var MAX_SESSIONS_FILES = 2000;
440
428
  var SAVED_SESSIONS_FILE_RE = /_([1-9]\d*)_(\d+)$/;
441
429
  var DIR_RE = /^\d{6}$/;
@@ -492,7 +480,9 @@ exports.getSavedList = function(savedDir, cb) {
492
480
  list.push(dir);
493
481
  }
494
482
  });
495
- list = list.sort(descSorter).slice(0, 12).map(function(dir) {
483
+ list = list.sort(function (a, b) {
484
+ return a > b ? -1 : 1;
485
+ }).slice(0, 12).map(function(dir) {
496
486
  return path.join(savedDir, dir);
497
487
  });
498
488
  readSessionsFiles(list, cb);
@@ -504,32 +494,3 @@ var SHARE_TYPE_RE = /Share$/;
504
494
  exports.isShareType = function(type) {
505
495
  return SHARE_TYPE_RE.test(type);
506
496
  };
507
-
508
- function writeRetry(filepath, data, callback, retry) {
509
- fse.outputFile(filepath, data, function(e) {
510
- if (!e || retry) {
511
- return callback(e);
512
- }
513
- writeRetry(filepath, data, callback, true);
514
- });
515
- }
516
-
517
- function writeFile(filepath, data, callback) {
518
- common.getStat(filepath, function(_, stat) {
519
- if (!stat || !stat.isFile()) {
520
- return writeRetry(filepath, data, callback);
521
- }
522
- callback();
523
- });
524
- }
525
-
526
- exports.writeFile = writeFile;
527
-
528
- function writeTempFile(dir, value, callback) {
529
- var filename = getHexHash(value);
530
- writeFile(path.join(dir, filename), value, function(err) {
531
- callback(err, filename);
532
- });
533
- }
534
-
535
- exports.writeTempFile = writeTempFile;
package/lib/socket-mgr.js CHANGED
@@ -7,28 +7,14 @@ var extend = require('extend');
7
7
  var pendingReqList = [];
8
8
  var INTERVAL = 22 * 1000;
9
9
  var proxy;
10
- var index = 0;
11
- var MAX_PAYLOAD = 1024 * 1024;
12
10
  var conns = {};
13
11
  var PING = Buffer.from('iQA=', 'base64');
14
12
  var PONG = Buffer.from('ioAn6ubf', 'base64');
15
13
  var PAUSE_STATUS = 1;
16
14
  var IGNORE_STATUS = 2;
17
15
  var MAX_COMPOSE_FRAME_COUNT = 5;
18
-
19
- function getFrameId() {
20
- ++index;
21
- if (index > 999) {
22
- index = 0;
23
- }
24
- if (index > 99) {
25
- return Date.now() + '-' + index;
26
- }
27
- if (index > 9) {
28
- return Date.now() + '-0' + index;
29
- }
30
- return Date.now() + '-00' + index;
31
- }
16
+ var getFrameId = util.getFrameId;
17
+ var MAX_PAYLOAD = util.MAX_FRAME_PAYLOAD;
32
18
 
33
19
  exports = module.exports = function (p) {
34
20
  proxy = p;
package/lib/tunnel.js CHANGED
@@ -91,6 +91,7 @@ function tunnelProxy(server, proxy, type) {
91
91
  reqSocket.headers = headers;
92
92
  reqSocket.fromTunnel = req.fromTunnel;
93
93
  reqSocket.fromComposer = req.fromComposer;
94
+ reqSocket._testId = req._testId;
94
95
  req.isPluginReq = reqSocket.isPluginReq = util.checkPluginReqOnce(req, true);
95
96
  util.onSocketEnd(reqSocket, function (err) {
96
97
  // 处理可能的证书校验出错
@@ -100,6 +101,7 @@ function tunnelProxy(server, proxy, type) {
100
101
  id: req.reqId,
101
102
  url: options.host,
102
103
  fc: req.fromComposer ? 1 : undefined,
104
+ testId: req._testId,
103
105
  startTime: now,
104
106
  dnsTime: now,
105
107
  captureError: true,
@@ -319,6 +321,7 @@ function tunnelProxy(server, proxy, type) {
319
321
  id: req.reqId,
320
322
  url: options.host,
321
323
  fc: req.fromComposer ? 1 : undefined,
324
+ testId: req._testId,
322
325
  startTime: Date.now(),
323
326
  rules: _rules,
324
327
  req: reqData,
package/lib/upgrade.js CHANGED
@@ -167,6 +167,7 @@ function upgradeHandler(req, socket) {
167
167
  socket.url = req.url;
168
168
  socket.fromTunnel = req.fromTunnel;
169
169
  socket.fromComposer = req.fromComposer;
170
+ socket._testId = req._testId;
170
171
  socket.enableXFF = req.enableXFF;
171
172
  socket._isInternalReq = req._isInternalReq;
172
173
  socket.isInternalUrl = req.isInternalUrl;
@@ -70,10 +70,16 @@ var ALLOW_ACK = 'x-whistle-allow-tunnel-ack';
70
70
  var PLUS_RE = /\+/g;
71
71
  var TOKEN = '\r\u0000\n\u0003\r';
72
72
  var PROP_SEP_RE = /(\\*)([|&]|\\[stnrfv])/g;
73
- var TEMP_FILE_RE = /^[\da-f]{64}$/;
73
+ var TEMP_FILE_RE = /^(?:\/?temp\/)?([\da-f]{64})(?:\.[\w.-]+)?$/;
74
+ var TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
75
+
76
+
77
+ exports.WHISTLE_UID_HEADER = '_x-whistle-uid_';
78
+
79
+ exports.getTempFile = function(str) {
80
+ var match = TEMP_FILE_RE.exec(str);
81
+ return match && match[1];
74
82
 
75
- exports.isTempFile = function(str) {
76
- return TEMP_FILE_RE.test(str);
77
83
  };
78
84
 
79
85
  var PROPS_CHAR = {
@@ -135,6 +141,7 @@ exports.parseQuery = function (str, sep, eq, escape) {
135
141
  return '';
136
142
  };
137
143
 
144
+ exports.TEMP_FILES_PATH = TEMP_FILES_PATH;
138
145
  exports.ALLOW_ACK = ALLOW_ACK;
139
146
  exports.CONN_TIMEOUT = CONN_TIMEOUT;
140
147
  exports.TGZ_FILE_NAME_RE = /^(?:[\w.~-]+-)?whistle\.[a-z\d_-]+-\d+\.\d+\.\d+[\w.-]*\.(?:tgz|tar(?:\.gz)?)$/;
@@ -481,14 +488,8 @@ exports.isConnect = function(options) {
481
488
  );
482
489
  };
483
490
 
484
- var NO_PROTO_RE = /[^a-zA-Z0-9.-]/;
485
-
486
491
  function hasProtocol(url) {
487
- var index = isString(url) ? url.indexOf('://') : -1;
488
- if (index <= 0) {
489
- return false;
490
- }
491
- return !NO_PROTO_RE.test(url.substring(0, index));
492
+ return /^[a-zA-Z0-9.-]+:\/\//.test(url);
492
493
  }
493
494
 
494
495
  function setProtocol(url, isHttps) {
@@ -1126,6 +1127,10 @@ function isNum(n) {
1126
1127
  return typeof n === 'number';
1127
1128
  }
1128
1129
 
1130
+ exports.trimStr = function(str) {
1131
+ return typeof str === 'string' ? str.trim() : '';
1132
+ };
1133
+
1129
1134
  exports.parsePlainText = function (text, resolveKeys) {
1130
1135
  var result;
1131
1136
  text.split(LINE_END_RE).forEach(function(line) {
@@ -2001,3 +2006,42 @@ exports.getSessionInfo = function(headers) {
2001
2006
  result._ = rawResult;
2002
2007
  return result;
2003
2008
  };
2009
+
2010
+ function writeRetry(filepath, data, callback, retry) {
2011
+ fse.outputFile(filepath, data, function(e) {
2012
+ if (!e || retry) {
2013
+ return callback(e);
2014
+ }
2015
+ writeRetry(filepath, data, callback, true);
2016
+ });
2017
+ }
2018
+
2019
+ function writeFile(filepath, data, callback) {
2020
+ getStat(filepath, function(_, stat) {
2021
+ if (!stat || !stat.isFile()) {
2022
+ return writeRetry(filepath, data, callback);
2023
+ }
2024
+ callback();
2025
+ });
2026
+ }
2027
+
2028
+ exports.writeFile = writeFile;
2029
+
2030
+ function getHexHash(ctn, key) {
2031
+ return crypto
2032
+ .createHmac('sha256', key || 'whistle_temp_files')
2033
+ .update(ctn || '').digest('hex').toLowerCase();
2034
+ }
2035
+
2036
+ exports.writeTempFile = function (value, callback) {
2037
+ var filename = getHexHash(value);
2038
+ writeFile(path.join(TEMP_FILES_PATH, filename), value, function(err) {
2039
+ callback(err, filename);
2040
+ });
2041
+ };
2042
+
2043
+ var COMMENT_RE = /#[^\r\n]*/g;
2044
+
2045
+ exports.removeComment = function(text) {
2046
+ return text.replace(COMMENT_RE, '').trim();
2047
+ };
@@ -577,6 +577,16 @@ module.exports = function init(_proxy) {
577
577
  }
578
578
  return item;
579
579
  };
580
+ proxy.getTestItem = function(id) {
581
+ var list = Object.values(reqData);
582
+ for (var i = 0, len = list.length; i < len; i++) {
583
+ var item = list[i];
584
+ if (item.testId === id) {
585
+ return item;
586
+ }
587
+ }
588
+ };
589
+
580
590
  proxy.abortRequest = function (id) {
581
591
  var item = id && reqData[id];
582
592
  item && item.abort && item.abort();