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
@@ -20,6 +20,7 @@ var common = require('../../../lib/util/common');
20
20
  var getWorker = require('../../../lib/plugins/util').getWorker;
21
21
  var loadAuthPlugins = require('../../../lib/plugins').loadAuthPlugins;
22
22
  var parseUrl = require('../../../lib/util/parse-url-safe');
23
+ var loadService = require('../../../lib/service');
23
24
 
24
25
  var sendError = cgiUtil.sendError;
25
26
  var sendGzipText = cgiUtil.sendGzipText;
@@ -55,6 +56,8 @@ var MENU_URL = '???_WHISTLE_PLUGIN_EXT_CONTEXT_MENU_' + config.port + '???';
55
56
  var INSPECTOR_URL = '???_WHISTLE_PLUGIN_INSPECTOR_TAB_' + config.port + '???';
56
57
  var KEY_RE_G = /\${[^{}\s]+}|{\S+}/g;
57
58
  var COMMENT_RE = /#[^\r\n]*$/mg;
59
+ var MAX_LEN = 1024 * 1024 * 6;
60
+ var HTTP_RE = /https?:\/\/\S/i;
58
61
 
59
62
  function hasLogin() {
60
63
  return config.username && config.password;
@@ -65,7 +68,7 @@ function isTempFile(query) {
65
68
  if (files && typeof files === 'string') {
66
69
  return false;
67
70
  }
68
- return common.isTempFile(query.filename);
71
+ return common.getTempFile(query.filename);
69
72
  }
70
73
 
71
74
  function sendToService(req, res) {
@@ -74,10 +77,11 @@ function sendToService(req, res) {
74
77
  res.end('{"ec":0,"value":"","forbidden":true}');
75
78
  return;
76
79
  }
77
- proxyEvent.loadService(function(err, options) {
80
+ loadService(function(err, options) {
78
81
  if (err) {
79
82
  common.sendRes(res, 500, err.stack || err);
80
83
  } else {
84
+ req.headers[common.WHISTLE_UID_HEADER] = config.uid;
81
85
  util.transformReq(req, res, options.port);
82
86
  }
83
87
  });
@@ -407,6 +411,32 @@ function cgiHandler(req, res) {
407
411
  handleResponse();
408
412
  });
409
413
  }
414
+ app.get('/cgi-bin/import-remote', function(req, res) {
415
+ var url = req.query.url;
416
+ if (HTTP_RE.test(url)) {
417
+ util.request({
418
+ url: url,
419
+ maxLength: MAX_LEN
420
+ }, function(err, body, r) {
421
+ if (err) {
422
+ var msg = err.code === 'EEXCEED' ? 'The size of response body exceeds 6MB' : err.message;
423
+ return res.json({ec: 2, em: msg});
424
+ }
425
+ var status = r.statusCode;
426
+ if (status !== 200) {
427
+ var em = status > 200 && status < 400 ? 'No data' : 'Request failed';
428
+ return res.json({ec: 2, em: em + ' (statusCode: ' + status + ')'});
429
+ }
430
+ return res.json({ec: 0, body: body});
431
+ });
432
+ } else if (util.isString(url)) {
433
+ req.url = '/cgi-bin/temp/get?filename=' + encodeURIComponent(url);
434
+ req.query.filename = url;
435
+ sendToService(req, res);
436
+ } else {
437
+ res.json({ec: 400, em: 'Bad url'});
438
+ }
439
+ });
410
440
  app.all('/service/*', sendToService);
411
441
  app.all('/cgi-bin/service/*', sendToService);
412
442
  app.all('/cgi-bin/sessions/*', sendToService);
package/lib/config.js CHANGED
@@ -91,6 +91,7 @@ config.WEBUI_HEAD = 'x-forwarded-from-whistle-' + uid;
91
91
  config.RES_RULES_HEAD = 'x-whistle-res-rules-' + uid;
92
92
  config.COMPOSER_CLIENT_ID_HEADER = 'x-whistle-client-id-' + uid;
93
93
  config.FROM_COM_HEADER = 'x-whistle-composer-' + uid;
94
+ config.TEST_HEADER = 'x-whistle-test-' + uid;
94
95
  config.PLUGIN_HOOKS = {
95
96
  SNI: 'sni-' + uid,
96
97
  AUTH: 'auth-' + uid,
@@ -148,7 +149,6 @@ function getDataDir(dirname) {
148
149
  }
149
150
 
150
151
  config.baseDir = getDataDir();
151
- config.TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
152
152
  config.CUSTOM_PLUGIN_PATH = path.join(getWhistlePath(), 'custom_plugins');
153
153
  config.CUSTOM_CERTS_DIR = path.resolve(getWhistlePath(), 'custom_certs');
154
154
  config.SAVED_SESSIONS_PATH = path.resolve(getWhistlePath(), 'saved_sessions');
@@ -158,8 +158,8 @@ config.valuesDir = path.join(config.baseDir, 'values');
158
158
  config.propertiesDir = path.join(config.baseDir, 'properties');
159
159
 
160
160
  try {
161
- fse.ensureDirSync(config.TEMP_FILES_PATH);
162
- var blankFile = path.join(config.TEMP_FILES_PATH, 'blank');
161
+ fse.ensureDirSync(common.TEMP_FILES_PATH);
162
+ var blankFile = path.join(common.TEMP_FILES_PATH, 'blank');
163
163
  if (!fs.existsSync(blankFile)) {
164
164
  fs.writeFileSync(blankFile, '');
165
165
  }
@@ -796,7 +796,7 @@ exports.extend = function (newConf) {
796
796
  setDefaultResultOrder(m.toLowerCase());
797
797
  } else if (m === 'proxyOnly') {
798
798
  config.pureProxy = true;
799
- } else if (m === 'useMultipleRules' || m === 'enableMultipleRules') {
799
+ } else if (m === 'useMultipleRules' || m === 'enableMultipleRules' || m === 'allowMultipleChoice') {
800
800
  config.allowMultipleChoice = true;
801
801
  } else if (m === 'disableMultipleRules') {
802
802
  config.allowMultipleChoice = false;
@@ -66,7 +66,7 @@ function parseRes(str, rawHeaderNames, fromValue) {
66
66
  };
67
67
  }
68
68
  var headers = str.split(CRLF_RE);
69
- var statusLine = headers.shift().trim().split(/\s+/g);
69
+ var statusLine = headers.shift().trim().split(/\s+/);
70
70
  headers = util.parseHeaders(headers, rawHeaderNames);
71
71
  if (fromValue) {
72
72
  delete headers['content-encoding'];
package/lib/https/ca.js CHANGED
@@ -177,6 +177,19 @@ exports.hasCustomCerts = function () {
177
177
  return customCertCount;
178
178
  };
179
179
 
180
+ var rootSkiBytes;
181
+
182
+ function getRootSKI() {
183
+ if (!rootSkiBytes) {
184
+ try {
185
+ rootSkiBytes = ROOT_CRT.generateSubjectKeyIdentifier().getBytes();
186
+ } catch (e) {
187
+ rootSkiBytes = null;
188
+ }
189
+ }
190
+ return rootSkiBytes;
191
+ }
192
+
180
193
  function getCacheCert(hostname) {
181
194
  return (
182
195
  customPairs[hostname] ||
@@ -206,6 +219,20 @@ function createSelfCert(hostname) {
206
219
  ]);
207
220
  cert.setIssuer(ROOT_CRT.subject.attributes);
208
221
  cert.setExtensions([
222
+ {
223
+ name: 'basicConstraints',
224
+ cA: false
225
+ },
226
+ {
227
+ name: 'keyUsage',
228
+ digitalSignature: true,
229
+ keyEncipherment: true
230
+ },
231
+ {
232
+ name: 'extKeyUsage',
233
+ serverAuth: true,
234
+ clientAuth: true
235
+ },
209
236
  {
210
237
  name: 'subjectAltName',
211
238
  altNames: [
@@ -219,6 +246,10 @@ function createSelfCert(hostname) {
219
246
  value: hostname
220
247
  }
221
248
  ]
249
+ },
250
+ {
251
+ name: 'authorityKeyIdentifier',
252
+ keyIdentifier: getRootSKI() // Get SKI from ROOT_CRT for authorityKeyIdentifier extension
222
253
  }
223
254
  ]);
224
255
  cert.sign(ROOT_KEY, forge.md.sha256.create());
@@ -106,6 +106,7 @@ function resolveWebsocket(socket, wss) {
106
106
  _clientId: socket._clientId,
107
107
  id: socket.reqId,
108
108
  fc: socket.fromComposer ? 1 : undefined,
109
+ testId: socket._testId,
109
110
  url: fullUrl,
110
111
  startTime: now,
111
112
  sniPlugin: socket.sniPlugin,
@@ -718,9 +719,10 @@ function resolveWebsocket(socket, wss) {
718
719
  extend(headers, reqHeaders);
719
720
  headers.host = headers.host || host;
720
721
  }
721
- auth = util.getAuthBasic(auth || authObj);
722
+ authObj = auth || authObj;
723
+ auth = util.getAuthBasic(authObj);
722
724
  if (auth) {
723
- headers['authorization'] = auth;
725
+ headers[(authObj.proxy ? 'proxy-' : '') + 'authorization'] = auth;
724
726
  }
725
727
  var reqRuleData = { headers: headers };
726
728
  util.setReqCors(reqRuleData, reqCors);
package/lib/index.js CHANGED
@@ -13,7 +13,6 @@ var rulesUtil = require('./rules/util');
13
13
  var initDataServer = require('./util/data-server');
14
14
  var pluginMgr = require('./plugins');
15
15
  var config = require('./config');
16
- var loadService = require('./service');
17
16
  var initSocketMgr = require('./socket-mgr');
18
17
  var tunnelProxy = require('./tunnel');
19
18
  var upgradeProxy = require('./upgrade');
@@ -222,7 +221,6 @@ function exportInterfaces(obj) {
222
221
  obj.httpsUtil = httpsUtil;
223
222
  obj.pluginMgr = pluginMgr;
224
223
  obj.logger = logger;
225
- obj.loadService = loadService;
226
224
  obj.setAuth = config.setAuth;
227
225
  obj.setUIHost = config.setUIHost;
228
226
  obj.setPluginUIHost = config.setPluginUIHost;
@@ -10,7 +10,9 @@ var MAX_REQ_BODY_SIZE = (config.strict ? 256 : 2048) * 1024;
10
10
  var MAX_RES_BODY_SIZE = (config.strict ? 256 : 2048) * 1024;
11
11
  var BIG_DATA_SIZE = 1024 * 1024 * 16;
12
12
  var LOCALHOST = '127.0.0.1';
13
- var SSE_RE = /^\s*text\/event-stream/i;
13
+ var MAX_PAYLOAD = util.MAX_FRAME_PAYLOAD;
14
+ var SSE_SEP = Buffer.from('\n\n');
15
+ var FRAME_SEP_KEY = 'x-whistle-custom-frame-separator';
14
16
 
15
17
  function getZipType(options) {
16
18
  return options.headers && options.headers['content-encoding'];
@@ -57,6 +59,80 @@ function emitDataEvents(req, res, proxy) {
57
59
  if (!util.showPluginReq(req) || util.isHide(req)) {
58
60
  return;
59
61
  }
62
+
63
+ var reqSepInfo = parseFrameSep(req.headers);
64
+ var cseSep = reqSepInfo && reqSepInfo.sep;
65
+ var resSepInfo;
66
+
67
+ function emitFrame(chunk, fromServer) {
68
+ proxy.emit('frame', {
69
+ reqId: req.reqId,
70
+ frameId: util.getFrameId(),
71
+ isClient: !fromServer,
72
+ length: chunk.length,
73
+ bin: chunk
74
+ });
75
+ }
76
+
77
+ function parseFrameSep(headers) {
78
+ var sep = headers[FRAME_SEP_KEY];
79
+ if (!util.isString(sep)) {
80
+ return;
81
+ }
82
+ var result = {};
83
+ delete headers[FRAME_SEP_KEY];
84
+ try {
85
+ sep = decodeURIComponent(sep);
86
+ } catch (e) {}
87
+ if (sep[0] === '/') {
88
+ result.keepSep = true;
89
+ sep = sep.slice(1);
90
+ }
91
+ if (sep) {
92
+ result.sep = Buffer.from(sep);
93
+ result.sepLen = sep.length;
94
+ }
95
+ return result;
96
+ }
97
+
98
+ function parseFrame(buf, isRes, end) {
99
+ var len = buf && buf.length;
100
+ if (!len) {
101
+ return null;
102
+ }
103
+ var sep, sepLen, keepSep;
104
+ var info = isRes ? resSepInfo : reqSepInfo;
105
+ if (info) {
106
+ sep = info.sep;
107
+ sepLen = info.sepLen;
108
+ keepSep = info.keepSep;
109
+ } else {
110
+ sep = SSE_SEP;
111
+ sepLen = 2;
112
+ }
113
+ var index = buf.indexOf(sep);
114
+ while (index !== -1) {
115
+ if (keepSep) {
116
+ index += sepLen;
117
+ }
118
+ index && emitFrame(buf.slice(0, index), isRes);
119
+ buf = buf.slice(keepSep ? index : index + sepLen);
120
+ index = buf.indexOf(sep);
121
+ }
122
+ len = buf.length;
123
+ if (!len) {
124
+ return null;
125
+ }
126
+ if (end) {
127
+ emitFrame(buf, isRes);
128
+ return null;
129
+ }
130
+ if (len > MAX_PAYLOAD) {
131
+ emitFrame(buf.slice(0, -1), isRes);
132
+ buf = buf.slice(-1);
133
+ }
134
+ return buf;
135
+ }
60
136
  var getTTFB = function () {
61
137
  if (req._ttfb >= now) {
62
138
  return req._ttfb - now;
@@ -82,6 +158,7 @@ function emitDataEvents(req, res, proxy) {
82
158
  h2Id: req._alpn,
83
159
  fc: req.fromComposer ? 1 : undefined,
84
160
  isPR: req.isPluginReq ? 1 : undefined,
161
+ testId: req._testId,
85
162
  _clientId: req._clientId,
86
163
  startTime: now,
87
164
  ttfb: getTTFB(),
@@ -167,12 +244,20 @@ function emitDataEvents(req, res, proxy) {
167
244
  var captureReqStream;
168
245
  var maxReqSize;
169
246
  var reqInfo;
247
+ var setReqBody = function (body, end) {
248
+ if (captureReqStream && cseSep) {
249
+ reqBody = parseFrame(reqBody, false, end);
250
+ } else {
251
+ reqData.body = reqBody;
252
+ }
253
+ };
170
254
  var updateReqInfo = function() {
171
255
  if (reqInfo && !captureReqStream) {
172
256
  captureReqStream = !disable.captureStream && (resDone || enable.captureStream) && !getZipType(reqInfo);
173
257
  maxReqSize = useBigData ? BIG_DATA_SIZE : (captureReqStream ? MAX_REQ_BODY_SIZE : MAX_SIZE);
174
- if (captureReqStream && reqBody && !reqData.body) {
175
- reqData.body = reqBody;
258
+ if (captureReqStream) {
259
+ data.isCse = !!cseSep;
260
+ reqBody && !reqData.body && setReqBody(reqBody);
176
261
  }
177
262
  }
178
263
  };
@@ -198,7 +283,7 @@ function emitDataEvents(req, res, proxy) {
198
283
  } else {
199
284
  reqBody = reqBody ? Buffer.concat([reqBody, chunk]) : chunk;
200
285
  if (captureReqStream) {
201
- reqData.body = reqBody;
286
+ setReqBody(reqBody);
202
287
  }
203
288
  if (reqBody.length > maxReqSize) {
204
289
  reqBody = false;
@@ -225,8 +310,8 @@ function emitDataEvents(req, res, proxy) {
225
310
  data.endTime = endTime;
226
311
  }
227
312
  reqBody = err ? util.getErrorStack(err) : body;
228
- reqData.body = reqBody;
229
- setUnzipSize(body, reqData);
313
+ setReqBody(reqBody, true);
314
+ !data.isCse && setUnzipSize(body, reqData);
230
315
  checkBodySize(reqData, useBigData);
231
316
  });
232
317
  updateEvent && proxy.emit(updateEvent, req.fullUrl, false, true);
@@ -239,9 +324,22 @@ function emitDataEvents(req, res, proxy) {
239
324
  }
240
325
  resDone = true;
241
326
  info = info || (res._needGunzip ? { statusCode: res.statusCode, headers: '' } : res);
242
- var captureStream = !disable.captureStream && (requestTime == null || enable.captureStream ||
243
- (info.headers && SSE_RE.test(info.headers['content-type']))) && !getZipType(info);
327
+ var isSse;
328
+ var captureStream;
329
+ if (!disable.captureStream && !getZipType(info)) {
330
+ var headers = info.headers;
331
+ captureStream = requestTime == null || enable.captureStream;
332
+ if (headers) {
333
+ isSse = headers['content-type'] === 'text/event-stream';
334
+ captureStream = captureStream || isSse;
335
+ resSepInfo = parseFrameSep(headers);
336
+ if (resSepInfo && resSepInfo.sep) {
337
+ isSse = captureStream;
338
+ }
339
+ }
340
+ }
244
341
  var maxResSize = useBigData ? BIG_DATA_SIZE : (captureStream ? MAX_RES_BODY_SIZE : MAX_SIZE);
342
+ data.isSse = isSse;
245
343
  captureStream && updateReqInfo();
246
344
  if (!cleared && util.hasBody(info, req) && checkType(info)) {
247
345
  if (info.headers['content-length'] > maxResSize && !captureStream) {
@@ -261,9 +359,13 @@ function emitDataEvents(req, res, proxy) {
261
359
  } else {
262
360
  resBody = resBody ? Buffer.concat([resBody, chunk]) : chunk;
263
361
  if (captureStream) {
264
- resData.body = resBody;
362
+ if (isSse) {
363
+ resBody = parseFrame(resBody, true);
364
+ } else {
365
+ resData.body = resBody;
366
+ }
265
367
  }
266
- if (resBody.length > maxResSize) {
368
+ if (resBody && resBody.length > maxResSize) {
267
369
  resBody = false;
268
370
  }
269
371
  }
@@ -286,9 +388,13 @@ function emitDataEvents(req, res, proxy) {
286
388
  resData.hasGzipError = unzipBody(info, resBody, function (err, body) {
287
389
  if (!data.resError) {
288
390
  resBody = err ? util.getErrorStack(err) : body;
289
- resData.body = resBody;
391
+ if (isSse) {
392
+ resBody = parseFrame(resBody, true, true);
393
+ } else {
394
+ resData.body = resBody;
395
+ }
290
396
  }
291
- setUnzipSize(body, resData);
397
+ !isSse && setUnzipSize(body, resData);
292
398
  checkBodySize(resData, useBigData);
293
399
  setEndTime();
294
400
  reqEmitter.emit('end', data);
@@ -341,7 +447,8 @@ function emitDataEvents(req, res, proxy) {
341
447
  }
342
448
  !endTime && setEndTime();
343
449
  delete data.abort;
344
- if (err && err.message !== 'Aborted') {
450
+ var msg = err && err.message;
451
+ if (msg !== 'Aborted') {
345
452
  data.resError = true;
346
453
  if (resData.body) {
347
454
  data.errMsg = util.getErrorStack(err);
@@ -147,9 +147,11 @@ function handleReq(req, data, reqRules, delType) {
147
147
  }
148
148
  }
149
149
 
150
- function handleAuth(data, auth) {
151
- auth = util.getAuthBasic(auth);
152
- auth && util.setHeader(data, 'authorization', auth);
150
+ function handleAuth(data, authObj) {
151
+ var auth = util.getAuthBasic(authObj);
152
+ if (auth) {
153
+ util.setHeader(data, (authObj.proxy ? 'proxy-' : '') + 'authorization', auth);
154
+ }
153
155
  }
154
156
 
155
157
  function handleParams(req, params, urlParams, enableBigData) {
@@ -12,6 +12,7 @@ var notUninstallPluginPaths = config.notUninstallPluginPaths || [];
12
12
  var projectPluginPaths = config.projectPluginPaths || [];
13
13
  var accountPluginsPath = config.accountPluginsPath || [];
14
14
  var paths = mp.getPaths();
15
+ var PLUGIN_DIR = pluginUtil.PLUGIN_DIR;
15
16
 
16
17
  function getMTimeSync(filePath) {
17
18
  var stats = common.getStatSync(filePath);
@@ -41,8 +42,9 @@ function readPluginModulesSync(dir, plugins) {
41
42
  var mtime = getMTimeSync(path.join(root, 'package.json'));
42
43
  if (mtime != null) {
43
44
  var old = plugins[name];
44
- if (!old || (mtime > old.mtime && root === old.root)) {
45
+ if (pluginUtil.compparePlugin(dir, mtime, old)) {
45
46
  plugins[name] = {
47
+ [PLUGIN_DIR]: dir,
46
48
  mtime: mtime,
47
49
  root: root,
48
50
  account: account,
@@ -65,8 +67,9 @@ function readPluginModulesSync(dir, plugins) {
65
67
  var mtime = getMTimeSync(path.join(root, 'package.json'));
66
68
  if (mtime != null) {
67
69
  var old = plugins[name];
68
- if (!old || (mtime > old.mtime && root === old.root)) {
70
+ if (pluginUtil.compparePlugin(dir, mtime, old)) {
69
71
  plugins[name] = {
72
+ [PLUGIN_DIR]: dir,
70
73
  root: root,
71
74
  mtime: mtime,
72
75
  account: account,
@@ -95,15 +98,15 @@ module.exports = function () {
95
98
  if (pluginUtil.excludePlugin(simpleName)) {
96
99
  return;
97
100
  }
98
- var dir = plugins[name];
99
- var account = dir.account;
100
- var isSys = dir.isSys;
101
- var isProj = dir.isProj;
102
- var notUn = dir.notUn;
103
- var isDev = dir.isDev;
104
- var dirName = dir.dir;
105
- var mtime = dir.mtime;
106
- dir = dir.root;
101
+ var obj = plugins[name];
102
+ var account = obj.account;
103
+ var isSys = obj.isSys;
104
+ var isProj = obj.isProj;
105
+ var notUn = obj.notUn;
106
+ var isDev = obj.isDev;
107
+ var dirName = obj.dir;
108
+ var mtime = obj.mtime;
109
+ var dir = obj.root;
107
110
  var pkgPath = path.join(dir, 'package.json');
108
111
  var pkg = common.readJsonSync(pkgPath);
109
112
  if (!pkg) {
@@ -120,6 +123,7 @@ module.exports = function () {
120
123
  var tabs = util.getInspectorTabs(conf);
121
124
  var hintList = util.getHintList(conf);
122
125
  var plugin = {
126
+ [PLUGIN_DIR]: obj[PLUGIN_DIR],
123
127
  account: account,
124
128
  dir: dirName,
125
129
  isSys: isSys,
@@ -9,6 +9,7 @@ var customPluginPaths = config.customPluginPaths || [];
9
9
  var notUninstallPluginPaths = config.notUninstallPluginPaths || [];
10
10
  var projectPluginPaths = config.projectPluginPaths || [];
11
11
  var accountPluginsPath = config.accountPluginsPath || [];
12
+ var PLUGIN_DIR = pluginUtil.PLUGIN_DIR;
12
13
 
13
14
  function readPluginRootList(dir, callback) {
14
15
  var roots = [];
@@ -31,7 +32,7 @@ function readPluginRootList(dir, callback) {
31
32
  if (pluginUtil.isWhistleModule(name)) {
32
33
  roots[i] = {
33
34
  name: name,
34
- dir: dir
35
+ [PLUGIN_DIR]: dir
35
36
  };
36
37
  } else if (pluginUtil.isOrgModule(name)) {
37
38
  ++count;
@@ -44,7 +45,7 @@ function readPluginRootList(dir, callback) {
44
45
  orgList.push({
45
46
  org: name,
46
47
  name: pluginName,
47
- dir: dir
48
+ [PLUGIN_DIR]: dir
48
49
  });
49
50
  }
50
51
  });
@@ -74,7 +75,7 @@ function readPluginModules(dir, callback, plugins, isSys) {
74
75
  return callback(plugins);
75
76
  }
76
77
  list.forEach(function (obj) {
77
- var dir = obj.dir;
78
+ var dir = obj[PLUGIN_DIR];
78
79
  var dirName = obj.org ? obj.org + '/' + obj.name : obj.name;
79
80
  pluginUtil.getSysPath(dir, dirName, isSys, function(root) {
80
81
  common.getStat(path.join(root, 'package.json'), function (_, stats) {
@@ -83,13 +84,10 @@ function readPluginModules(dir, callback, plugins, isSys) {
83
84
  obj.mtime = stats.mtime.getTime();
84
85
  }
85
86
  if (--len === 0) {
86
- list.forEach(function (obj) {
87
- var name = pluginUtil.getPluginName(obj.name);
88
- if (obj.root) {
89
- var old = plugins[name];
90
- if (!old || (obj.mtime > old.mtime && obj.root === old.root)) {
91
- plugins[name] = obj;
92
- }
87
+ list.forEach(function (item) {
88
+ var name = pluginUtil.getPluginName(item.name);
89
+ if (item.root && pluginUtil.compparePlugin(dir, item.mtime, plugins[name])) {
90
+ plugins[name] = item;
93
91
  }
94
92
  });
95
93
  callback(plugins);
@@ -121,10 +119,11 @@ module.exports = function (callback) {
121
119
  if (pluginUtil.excludePlugin(name)) {
122
120
  return;
123
121
  }
124
- var old = result[name + ':'];
125
122
  var obj = plugins[name];
126
- if (!old || (obj.mtime > old.mtime && obj.root === old.path)) {
123
+ var dir = obj[PLUGIN_DIR];
124
+ if (pluginUtil.compparePlugin(dir, obj.mtime, result[name + ':'])) {
127
125
  result[name + ':'] = {
126
+ [PLUGIN_DIR]: dir,
128
127
  account: account,
129
128
  dir: config.whistleName,
130
129
  isSys: isSys,
@@ -1202,7 +1202,7 @@ module.exports = async function (options, callback) {
1202
1202
  PROXY_ID_HEADER = config.PROXY_ID_HEADER;
1203
1203
  options.getTempFilePath = function(filePath) {
1204
1204
  if (common.TEMP_PATH_RE.test(filePath)) {
1205
- return path.join(config.TEMP_FILES_PATH, RegExp.$1);
1205
+ return path.join(common.TEMP_FILES_PATH, RegExp.$1);
1206
1206
  }
1207
1207
  };
1208
1208
 
@@ -36,6 +36,7 @@ var pluginPaths = config.pluginPaths;
36
36
  if (pluginPaths) {
37
37
  pluginPaths = pluginPaths.concat(pluginPaths.map(formatPath).concat(addon));
38
38
  pluginPaths = prePlugins.concat(pluginPaths);
39
+ config.PLUGIN_INSTALL_ROOT = pluginPaths[0];
39
40
  addDebugPaths(pluginPaths);
40
41
  pluginPaths = uniqueArr(pluginPaths);
41
42
  exports.getPaths = function () {
@@ -80,6 +81,9 @@ if (pluginsPath && paths.indexOf(pluginsPath) == -1) {
80
81
  }
81
82
  if (!config.customPluginPaths || !config.customPluginPaths.length) {
82
83
  paths.unshift(config.CUSTOM_PLUGIN_PATH);
84
+ config.PLUGIN_INSTALL_ROOT = config.CUSTOM_PLUGIN_PATH;
85
+ } else {
86
+ config.PLUGIN_INSTALL_ROOT = config.customPluginPaths[0];
83
87
  }
84
88
  paths = addon.concat(paths);
85
89
  addDebugPaths(paths);
@@ -13,11 +13,14 @@ var MAX_EXPIRE = 36000;
13
13
  var UTF8_OPTIONS = { encoding: 'utf8' };
14
14
  var workerScript = util.readFileSync(path.join(__dirname, '../../assets/js/worker.js'), false);
15
15
  var workers = {};
16
+ var PLUGIN_DIR = Symbol('dir');
16
17
 
17
18
  function replaceScript(ctn, name) {
18
19
  return workerScript.replace('$PLUGIN_NAME', name).replace('/*sourcecode*/', ctn);
19
20
  }
20
21
 
22
+ exports.PLUGIN_DIR = PLUGIN_DIR;
23
+
21
24
  exports.readWorkerSync = function(root, conf, name) {
22
25
  var file = util.getWebWorker(conf);
23
26
  file = file && util.readFileSync(path.join(root, file));
@@ -140,6 +143,7 @@ function setPlugin(plugins, pkg, root, mtime, sync) {
140
143
  if (PLUGIN_NAME_RE.test(pkg.name)) {
141
144
  var name = sync ? RegExp.$1 : getPluginName(RegExp.$1);
142
145
  plugins[name] = {
146
+ [PLUGIN_DIR]: DEV_PLUGINS_PATH,
143
147
  root: root,
144
148
  mtime: mtime,
145
149
  notUn: true,
@@ -256,3 +260,7 @@ exports.getSysPath = function(dir, name, isSys, cb) {
256
260
  cb(exists ? sysRoot : root);
257
261
  });
258
262
  };
263
+
264
+ exports.compparePlugin = function (dir, mtime, old) {
265
+ return !old || (mtime > old.mtime && dir === old[PLUGIN_DIR]);
266
+ };
package/lib/rules/dns.js CHANGED
@@ -177,3 +177,7 @@ module.exports = function lookup(hostname, callback, allowDnsCache) {
177
177
  err ? lookupDNS(hostname, callback) : callback(err, ip);
178
178
  });
179
179
  };
180
+
181
+ module.exports.clearCache = function() {
182
+ dnsCache = {};
183
+ };