whistle 2.10.9 → 2.10.10

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.
@@ -35,6 +35,7 @@ var uploadUrlencodedParser = bodyParser.urlencoded(UPLOAD_PARSE_CONF);
35
35
  var uploadJsonParser = bodyParser.json(UPLOAD_PARSE_CONF);
36
36
  var GET_METHOD_RE = /^get$/i;
37
37
  var WEINRE_RE = /^\/weinre\/.*/;
38
+ var IMPORT_CGI = '/cgi-bin/import-remote';
38
39
  var ALLOW_PLUGIN_PATHS = ['/cgi-bin/rules/list2', '/cgi-bin/values/list2', '/cgi-bin/get-custom-certs-info'];
39
40
  var DONT_CHECK_PATHS = ['/cgi-bin/server-info', '/cgi-bin/plugins/is-enable', '/cgi-bin/plugins/get-plugins',
40
41
  '/preview.html', '/cgi-bin/rootca', '/cgi-bin/check-update', '/cgi-bin/log/set', '/cgi-bin/status'];
@@ -70,11 +71,14 @@ function isTempFile(query) {
70
71
  return common.getTempFile(query.filename);
71
72
  }
72
73
 
74
+ function sendForbidden(res) {
75
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
76
+ res.end('{"ec":0,"value":"","forbidden":true}');
77
+ }
78
+
73
79
  function sendToService(req, res) {
74
80
  if (!hasLogin() && req.path === '/cgi-bin/temp/get' && !isTempFile(req.query)) {
75
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
76
- res.end('{"ec":0,"value":"","forbidden":true}');
77
- return;
81
+ return sendForbidden(res);
78
82
  }
79
83
  loadService(function(err, options) {
80
84
  if (err) {
@@ -135,6 +139,9 @@ function requireLogin(req, res, msg) {
135
139
  }
136
140
  return res.status(404).end();
137
141
  }
142
+ if (req.path === IMPORT_CGI) {
143
+ return sendForbidden(res);
144
+ }
138
145
  res.setHeader('WWW-Authenticate', ' Basic realm=User Login');
139
146
  res.setHeader('Content-Type', 'text/html; charset=utf8');
140
147
  res.status(401).end(msg || 'Access denied, please <a href="javascript:;" onclick="location.reload()">try again</a>');
@@ -410,32 +417,7 @@ function cgiHandler(req, res) {
410
417
  handleResponse();
411
418
  });
412
419
  }
413
- app.get('/cgi-bin/import-remote', function(req, res) {
414
- var url = req.query.url;
415
- if (HTTP_RE.test(url)) {
416
- util.request({
417
- url: url,
418
- maxLength: MAX_LEN
419
- }, function(err, body, r) {
420
- if (err) {
421
- var msg = err.code === 'EEXCEED' ? 'The size of response body exceeds 6MB' : err.message;
422
- return res.json({ec: 2, em: msg});
423
- }
424
- var status = r.statusCode;
425
- if (status !== 200) {
426
- var em = status > 200 && status < 400 ? 'No data' : 'Request failed';
427
- return res.json({ec: 2, em: em + ' (statusCode: ' + status + ')'});
428
- }
429
- return res.json({ec: 0, body: body});
430
- });
431
- } else if (util.isString(url)) {
432
- req.url = '/cgi-bin/temp/get?filename=' + encodeURIComponent(url);
433
- req.query.filename = url;
434
- sendToService(req, res);
435
- } else {
436
- res.json({ec: 400, em: 'Bad url'});
437
- }
438
- });
420
+
439
421
  app.all('/service/*', sendToService);
440
422
  app.all('/cgi-bin/service/*', sendToService);
441
423
  app.all('/cgi-bin/sessions/*', sendToService);
@@ -448,7 +430,7 @@ function readPluginPage(req, res, plugin, html, config) {
448
430
  res.write(config);
449
431
  res.write(html);
450
432
  var index = req.path.indexOf('/', 1);
451
- if (index === -1) {
433
+ if (index === -1 || common.existsUpPath(req.path)) {
452
434
  res.end();
453
435
  } else {
454
436
  var filepath = req.path.substring(index + 1);
@@ -528,6 +510,33 @@ app.use(function(req, res, next) {
528
510
  }
529
511
  });
530
512
 
513
+ app.get(IMPORT_CGI, function(req, res) {
514
+ var url = req.query.url;
515
+ if (HTTP_RE.test(url)) {
516
+ util.request({
517
+ url: url,
518
+ maxLength: MAX_LEN
519
+ }, function(err, body, r) {
520
+ if (err) {
521
+ var msg = err.code === 'EEXCEED' ? 'The size of response body exceeds 6MB' : err.message;
522
+ return res.json({ec: 2, em: msg});
523
+ }
524
+ var status = r.statusCode;
525
+ if (status !== 200) {
526
+ var em = status > 200 && status < 400 ? 'No data' : 'Request failed';
527
+ return res.json({ec: 2, em: em + ' (statusCode: ' + status + ')'});
528
+ }
529
+ return res.json({ec: 0, body: body});
530
+ });
531
+ } else if (util.isString(url)) {
532
+ req.url = '/cgi-bin/temp/get?filename=' + encodeURIComponent(url);
533
+ req.query.filename = url;
534
+ sendToService(req, res);
535
+ } else {
536
+ res.json({ec: 400, em: 'Bad url'});
537
+ }
538
+ });
539
+
531
540
  app.post('/cgi-bin/composer', function(req, res) {
532
541
  req.headers[config.CLIENT_IP_HEADER] = util.getClientIp(req);
533
542
  req.headers[config.CLIENT_PORT_HEADER] = util.getClientPort(req);
package/lib/config.js CHANGED
@@ -26,7 +26,6 @@ var httpsAgents = new LRU({ max: 360 });
26
26
  var socksAgents = new LRU({ max: 100 });
27
27
  var version = process.version.substring(1).split('.');
28
28
  var disableAgent = version[0] > 10;
29
- var useVerbatim = version[0] >= 17;
30
29
  var hasIPv6First = version[0] >= 22 && version[1] >= 1;
31
30
  var hasDnsOrder = typeof dns.setDefaultResultOrder === 'function';
32
31
  var uid = Date.now().toString(16) + (process.pid * 10000 + Math.floor(Math.random() * 10000)).toString(16);
@@ -61,15 +60,10 @@ var variableProperties = [
61
60
  'disableInstaller'
62
61
  ];
63
62
  if (hasDnsOrder) {
64
- if (hasIPv6First) {
65
- config.verbatim = 2;
66
- } else {
67
- config.verbatim = 1;
68
- }
63
+ config.verbatim = hasIPv6First ? 2 : 1;
69
64
  }
70
65
 
71
66
  config.appName = common.upperFirst(config.name);
72
- config.defaultDnsOrder = useVerbatim ? 1 : 2;
73
67
  config.uid = uid;
74
68
  config.rejectUnauthorized = false;
75
69
  config.enableH2 = version[0] > 12; // 支持 HTTP2 要求的最低 Node 版本
@@ -119,7 +113,7 @@ config.PLUGIN_HOOKS = {
119
113
 
120
114
  var KEEP_ALIVE_MSECS = 10000;
121
115
  var CONN_TIMEOUT = common.CONN_TIMEOUT;
122
- var DNS_ORDERS_OPTS = ['', 'verbatim', 'ipv4first', hasIPv6First ? 'ipv6first' : ''];
116
+ var DNS_ORDERS_OPTS = ['', 'ipv4first', 'ipv4first', hasIPv6First ? 'ipv6first' : '', '', 'verbatim'];
123
117
  var DOMAIN_STAR_RE = /([*~]+)(\\.)?/g;
124
118
 
125
119
  config.CONN_TIMEOUT = CONN_TIMEOUT;
@@ -305,13 +299,16 @@ function getAuths(_url) {
305
299
 
306
300
  exports.getAuths = getAuths;
307
301
 
302
+ function getDnsOrder(order) {
303
+ return order == 1 ? 2 : order;
304
+ }
305
+
308
306
  function setDefaultResultOrder(order) {
309
307
  if (!hasDnsOrder) {
310
308
  return;
311
309
  }
312
- order = DNS_ORDERS_OPTS.indexOf(order) === -1 ? DNS_ORDERS_OPTS[order] : order;
313
- if (!order) {
314
- return;
310
+ if (DNS_ORDERS_OPTS.indexOf(order) === -1) {
311
+ order = DNS_ORDERS_OPTS[order] || DNS_ORDERS_OPTS[1];
315
312
  }
316
313
  config.ipv4First = false;
317
314
  config.ipv6First = false;
@@ -321,10 +318,10 @@ function setDefaultResultOrder(order) {
321
318
  config.ipv4First = true;
322
319
  }
323
320
  try {
324
- if (dns.getDefaultResultOrder() !== order) {
321
+ if (!dns.getDefaultResultOrder || dns.getDefaultResultOrder() !== order) {
325
322
  dns.setDefaultResultOrder(order);
326
323
  }
327
- config.dnsOrder = DNS_ORDERS_OPTS.indexOf(order);
324
+ config.dnsOrder = getDnsOrder(DNS_ORDERS_OPTS.indexOf(order));
328
325
  return true;
329
326
  } catch (e) {}
330
327
  }
@@ -157,7 +157,7 @@ function getRawResByPath(protocol, path, req, size, callback, body) {
157
157
  }
158
158
  });
159
159
  reader.on('error', response);
160
- reader.on('end', response);
160
+ reader.once('end', response);
161
161
  } else {
162
162
  callback(reader, range, size);
163
163
  }
package/lib/https/ca.js CHANGED
@@ -43,9 +43,9 @@ var ILEGAL_CHAR_RE = /[^a-z\d-]/i;
43
43
  // https://cloud.tencent.com/developer/ask/sof/58155
44
44
  var RANDOM_SERIAL = '/' + Date.now() + '/' + process.pid + '/' + Math.floor(Math.random() * 100);
45
45
  var ONE_DAY = 1000 * 60 * 60 * 24;
46
- var MIN_DATE = ONE_DAY * 20;
47
- var CLEAR_CERTS_INTERVAL = ONE_DAY * 20;
48
- var MAX_INNTERFAL = 16;
46
+ var ONE_YEAR = ONE_DAY * 365;
47
+ var MIN_DATE = ONE_DAY * 7;
48
+ var MAX_DATE = ONE_DAY * 36;
49
49
  var PORT_RE = /:\d*$/;
50
50
  var pureCertFiles = {};
51
51
  var customRoot;
@@ -71,13 +71,16 @@ function resetAllCerts() {
71
71
  }
72
72
 
73
73
  // When delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer.
74
- var intervalCount = 0;
74
+ var startTime = Date.now();
75
+ var INTERVAL = ONE_DAY;
76
+ var MAX_AGE = MAX_DATE - INTERVAL * 2;
75
77
  var timer = setInterval(function () {
76
- if (++intervalCount >= MAX_INNTERFAL) {
77
- intervalCount = 0;
78
+ var now = Date.now();
79
+ if (now - startTime >= MAX_AGE) {
80
+ startTime = now;
78
81
  resetAllCerts();
79
82
  }
80
- }, CLEAR_CERTS_INTERVAL);
83
+ }, INTERVAL);
81
84
 
82
85
  if (timer && typeof timer.unref === 'function') {
83
86
  timer.unref();
@@ -553,18 +556,16 @@ function createCert(publicKey, serialNumber, isShortPeriod) {
553
556
  var cert = pki.createCertificate();
554
557
  cert.publicKey = publicKey;
555
558
  cert.serialNumber = serialNumber || '01';
556
- var curDate = new Date();
557
- var curYear = curDate.getFullYear();
559
+ var now = Date.now();
560
+ // https://www.ssls.com/blog/apples-new-ssl-lifetime-limitation-and-what-it-means-for-you/
561
+ // https://chromium.googlesource.com/chromium/src/+/refs/heads/master/net/cert/cert_verify_proc.cc#900
558
562
  if (isShortPeriod) {
559
- cert.validity.notBefore = new Date(curDate.getTime() - MIN_DATE);
563
+ cert.validity.notBefore = new Date(now - MIN_DATE);
564
+ cert.validity.notAfter = new Date(now + MAX_DATE);
560
565
  } else {
561
- cert.validity.notBefore = new Date();
562
- cert.validity.notBefore.setFullYear(curYear - 1);
566
+ cert.validity.notBefore = new Date(now - ONE_YEAR);
567
+ cert.validity.notAfter = new Date(now + ONE_YEAR * 10);
563
568
  }
564
- // https://www.ssls.com/blog/apples-new-ssl-lifetime-limitation-and-what-it-means-for-you/
565
- // https://chromium.googlesource.com/chromium/src/+/refs/heads/master/net/cert/cert_verify_proc.cc#900
566
- cert.validity.notAfter = new Date();
567
- cert.validity.notAfter.setFullYear(curYear + (isShortPeriod ? 1 : 10));
568
569
  return cert;
569
570
  }
570
571
 
package/lib/https/h2.js CHANGED
@@ -33,33 +33,43 @@ function onClose(stream, callback) {
33
33
  });
34
34
  }
35
35
 
36
+ function closeClose(client, name) {
37
+ try {
38
+ client.close();
39
+ } catch (e) {}
40
+ delete clients[name];
41
+ }
42
+
36
43
  setInterval(function () {
37
44
  var now = Date.now();
38
45
  Object.keys(clients).forEach(function (name) {
39
46
  var client = clients[name];
40
- if (now - client._updateTime > CACHE_TIMEOUT) {
41
- client.close();
42
- delete clients[name];
47
+ if (isClosed(client) || now - client._updateTime > CACHE_TIMEOUT) {
48
+ closeClose(client, name);
43
49
  }
44
50
  });
45
51
  }, INTERVAL);
46
52
 
47
- function getKey(options) {
53
+ function getOrigin(req) {
54
+ return (req.useHttpH2 ? 'http' : 'https') + '://' + req.headers.host;
55
+ }
56
+
57
+ function getKey(options, req) {
48
58
  var proxyOpts = options._proxyOptions;
49
59
  var proxyType = '';
50
60
  if (proxyOpts) {
51
- var auth =
52
- (proxyOpts.headers && proxyOpts.headers['proxy-authorization']) || '';
61
+ var headers = proxyOpts.headers || {};
53
62
  proxyType = [
54
63
  proxyOpts.proxyType,
55
64
  proxyOpts.proxyHost,
56
65
  proxyOpts.proxyPort,
57
- proxyOpts.headers.host,
66
+ headers.host,
58
67
  proxyOpts.proxyTunnelPath || '',
59
- auth
68
+ headers['proxy-authorization'] || ''
60
69
  ].join(':');
61
70
  }
62
71
  return [
72
+ getOrigin(req),
63
73
  options.servername,
64
74
  options.host,
65
75
  options.port || '',
@@ -222,35 +232,35 @@ function getSocket(options, callback, req) {
222
232
  }
223
233
 
224
234
  function getClient(req, socket, name, callback) {
225
- var origin = (req.useHttpH2 ? 'http' : 'https') + '://' + req.headers.host;
226
235
  var handleCallback = function(err, clt) {
227
236
  if (callback) {
228
237
  clearTimeout(timer);
229
238
  if (err || !clt) {
230
- delete clients[name];
239
+ closeClose(client, name);
231
240
  socket.destroy();
232
- client.close();
233
241
  }
234
242
  callback(clt);
235
243
  callback = timer = null;
236
244
  }
237
245
  };
238
246
  var timer = setTimeout(handleCallback, REQ_TIMEOUT);
239
- var client = http2.connect(
240
- origin, util.setRejectUnauthorized(req, {
241
- settings: H2_SETTINGS,
242
- maxSessionMemory: MSM,
243
- peerMaxConcurrentStreams: PMCS,
244
- streamResetBurst: RESET_BURST,
245
- streamResetRate: RESET_RATE,
246
- createConnection: function () {
247
- return socket;
248
- }
249
- }), function() {
250
- handleCallback(null, client);
251
- });
247
+ var client = http2.connect(getOrigin(req), util.setRejectUnauthorized(req, {
248
+ settings: H2_SETTINGS,
249
+ maxSessionMemory: MSM,
250
+ peerMaxConcurrentStreams: PMCS,
251
+ streamResetBurst: RESET_BURST,
252
+ streamResetRate: RESET_RATE,
253
+ createConnection: function () {
254
+ return socket;
255
+ }
256
+ }), function() {
257
+ handleCallback(null, client);
258
+ });
252
259
  clients[name] = client;
253
260
  client._updateTime = Date.now();
261
+ client.once('goaway', function() {
262
+ closeClose(client, name);
263
+ });
254
264
  onClose(client, handleCallback);
255
265
  onClose(socket, handleCallback);
256
266
  return client;
@@ -261,11 +271,6 @@ function requestH2(client, req, res, callback) {
261
271
  return;
262
272
  }
263
273
  var headers = util.formatH2Headers(req.headers);
264
- delete req.headers.connection;
265
- delete req.headers['keep-alive'];
266
- delete req.headers['http2-settings'];
267
- delete req.headers['proxy-connection'];
268
- delete req.headers['transfer-encoding'];
269
274
  var options = req.options;
270
275
  var responsed;
271
276
  headers[':path'] = options.path;
@@ -327,8 +332,10 @@ function requestH2(client, req, res, callback) {
327
332
  });
328
333
  req.pipe(h2Session);
329
334
  } catch (e) {
330
- !responsed && callback();
331
- responsed = true;
335
+ if (!responsed) {
336
+ callback();
337
+ responsed = true;
338
+ }
332
339
  }
333
340
  }
334
341
 
@@ -380,12 +387,16 @@ function checkTlsError(err) {
380
387
  return typeof code === 'string' && (code.indexOf('ERR_TLS_') === 0 || code.indexOf('ERR_SSL_') === 0);
381
388
  }
382
389
 
390
+ function isClosed(client) {
391
+ return client.destroyed || client.closed;
392
+ }
393
+
383
394
  exports.request = function (req, res, callback) {
384
395
  var options = req.useH2 && req.options;
385
396
  if (!options || options.isPlugin || req._isInternalProxy) {
386
397
  return callback();
387
398
  }
388
- var key = getKey(options);
399
+ var key = getKey(options, req);
389
400
  var reqId = req.disable.keepH2Session ? '' : req._h2ReqId;
390
401
  var name = (reqId || req.clientIp) + '\n' + key;
391
402
  var client = clients[name];
@@ -395,13 +406,21 @@ exports.request = function (req, res, callback) {
395
406
  name = name + '\n' + client.curIndex;
396
407
  client.curIndex = ++client.curIndex % CONCURRENT;
397
408
  client = clients[name];
409
+ if (client && isClosed(client)) {
410
+ delete clients[name];
411
+ client = null;
412
+ }
398
413
  } else {
399
414
  client.curIndex = 1;
400
415
  }
401
416
  }
402
417
  if (client) {
403
- client._updateTime = Date.now();
404
- return requestH2(client, req, res, callback);
418
+ if (!isClosed(client)) {
419
+ client._updateTime = Date.now();
420
+ return requestH2(client, req, res, callback);
421
+ }
422
+ delete clients[name];
423
+ client = null;
405
424
  }
406
425
  }
407
426
  var time = notH2.peek(key);
@@ -1177,6 +1177,12 @@ module.exports = function (socket, next, isWebPort) {
1177
1177
  headersStr = chunk && chunk.toString();
1178
1178
  var isHttp = chunk && HTTP_RE.test(headersStr);
1179
1179
  var statusLine = isHttp && RegExp['$&'];
1180
+ var handlePipe = function() {
1181
+ reqSocket.on('error', destroy);
1182
+ reqSocket.write(chunk);
1183
+ reqSocket.pipe(socket).pipe(reqSocket);
1184
+ socket.resume();
1185
+ };
1180
1186
  if (isHttp && CONNECT_RE.test(RegExp.$1)) {
1181
1187
  chunk = addClientInfo(socket, chunk, statusLine, clientIp, clientPort);
1182
1188
  util.connect(
@@ -1189,10 +1195,7 @@ module.exports = function (socket, next, isWebPort) {
1189
1195
  if (err || socket._hasError) {
1190
1196
  return destroy(err);
1191
1197
  }
1192
- reqSocket.on('error', destroy);
1193
- reqSocket.write(chunk);
1194
- reqSocket.pipe(socket).pipe(reqSocket);
1195
- socket.resume();
1198
+ handlePipe();
1196
1199
  }
1197
1200
  );
1198
1201
  return;
@@ -1248,12 +1251,10 @@ module.exports = function (socket, next, isWebPort) {
1248
1251
  if (err || socket._hasError) {
1249
1252
  return destroy(err);
1250
1253
  }
1251
- reqSocket.on('error', destroy);
1252
- socket._w2TunnelKey = reqSocket.localPort + ':' + reqSocket.remotePort;
1253
- tunnelTmplData.set(socket._w2TunnelKey, getTunnelData(socket, clientIp, clientPort, isHttpH2));
1254
- reqSocket.write(chunk);
1255
- reqSocket.pipe(socket).pipe(reqSocket);
1256
- socket.resume();
1254
+ var key = reqSocket.localPort + ':' + reqSocket.remotePort;
1255
+ socket._w2TunnelKey = key;
1256
+ tunnelTmplData.set(key, getTunnelData(socket, clientIp, clientPort, isHttpH2));
1257
+ handlePipe();
1257
1258
  }
1258
1259
  );
1259
1260
  };
@@ -419,7 +419,7 @@ function emitDataEvents(req, res, proxy) {
419
419
  }
420
420
 
421
421
  req.once('dest', function (_req) {
422
- _req.once('finish', function () {
422
+ util.onWriteEnd(_req, function () {
423
423
  if (!requestTime) {
424
424
  setRequestTime(Date.now());
425
425
  }
@@ -441,15 +441,19 @@ function emitDataEvents(req, res, proxy) {
441
441
  req.once('error', handleError);
442
442
  res.once('error', handleError);
443
443
  res.once('close', handleError);
444
- res.once('finish', setEndTime);
444
+ util.onWriteEnd(res, setEndTime);
445
445
 
446
446
  function handleError(err) {
447
447
  req._hasError = true;
448
448
  util.clearDelayTimer(req);
449
- if (!data || endTime || data.endTime || (data.responseTime && !err)) {
449
+ if (!data || endTime || data.endTime) {
450
450
  return;
451
451
  }
452
+ // 触发 endTime
452
453
  !endTime && setEndTime();
454
+ if (data.responseTime && !err) {
455
+ return;
456
+ }
453
457
  delete data.abort;
454
458
  // 不能乱优化,跟 var msg = err && err.message; if (msg !== 'Aborted' && msg !== 'aborted') 不一样
455
459
  if (err && err.message !== 'Aborted' && err.message !== 'aborted') {
@@ -7,7 +7,6 @@ var logScriptFile = path.join(config.ASSESTS_PATH, 'js/log.js');
7
7
  var logScript = fs.readFileSync(logScriptFile, { encoding: 'utf8' });
8
8
  var logScriptTag = '<script>' + logScript + '</script>\r\n';
9
9
  var logHtmlScript = '<!DOCTYPE html>\r\n' + logScriptTag;
10
- var LOG_ID_RE = /^log:\/\/(\{[^\s]{1,36}\}|[^/\\{}()<>\s]{1,36})$/;
11
10
 
12
11
  function wrapScript(script, isHtml) {
13
12
  return isHtml
@@ -53,39 +52,21 @@ module.exports = function (req, res) {
53
52
  transform._transform = function (chunk, _, callback) {
54
53
  if (!added) {
55
54
  added = true;
56
- var logId = '';
57
- var isValue;
58
- if (LOG_ID_RE.test(log.matcher)) {
59
- logId = RegExp.$1;
60
- if (logId[0] === '{') {
61
- logId = logId.slice(1, -1);
62
- isValue = true;
63
- }
64
- try {
65
- logId = encodeURIComponent(logId);
66
- } catch (e) {}
67
- }
68
- util.getRuleValue(
69
- logId && !isValue ? null : log,
70
- function (script) {
71
- topScript = topScript.replace('$LOG_ID', logId);
72
- var buf = [util.toBuffer(topScript)];
73
- userScript = script || null;
74
- if (userScript) {
75
- userScript = util.toBuffer(wrapScript(userScript, isHtml));
76
- if (isHtml || !chunk) {
77
- buf.push(userScript);
78
- userScript = null;
79
- }
55
+ var logId = util.removeProtocol(log.matcher, true);
56
+ util.getRuleValue(!logId || /^[^/\\{}()<>]+$/.test(logId) ? null : log, function (script) {
57
+ topScript = topScript.replace('$LOG_ID', logId && util.encodeURIComponent(logId));
58
+ var buf = [util.toBuffer(topScript)];
59
+ userScript = script || null;
60
+ if (userScript) {
61
+ userScript = util.toBuffer(wrapScript(userScript, isHtml));
62
+ if (isHtml || !chunk) {
63
+ buf.push(userScript);
64
+ userScript = null;
80
65
  }
81
- chunk && buf.push(chunk);
82
- callback(null, Buffer.concat(buf));
83
- },
84
- null,
85
- null,
86
- null,
87
- req
88
- );
66
+ }
67
+ chunk && buf.push(chunk);
68
+ callback(null, Buffer.concat(buf));
69
+ }, null, null, null, req);
89
70
  } else {
90
71
  callback(null, chunk || userScript);
91
72
  }
@@ -798,7 +798,7 @@ module.exports = function (req, res, next) {
798
798
  var useFrames;
799
799
  res.headers = req.resHeaders = headers;
800
800
  res._originEncoding = headers['content-encoding'];
801
- req.statusCode = _res.statusCode;
801
+ req.statusCode = res.statusCode = _res.statusCode;
802
802
  if (_res.rawHeaderNames) {
803
803
  res.rawHeaderNames = _res.rawHeaderNames;
804
804
  } else {
@@ -1243,7 +1243,7 @@ module.exports = function (req, res, next) {
1243
1243
  _res.statusCode,
1244
1244
  formatHeaders(curHeaders, rawNames)
1245
1245
  );
1246
- util.onResEnd(_res, function () {
1246
+ util.onReadEnd(_res, function () {
1247
1247
  var trailers = _res.trailers;
1248
1248
  if (
1249
1249
  !res.chunkedEncoding ||
@@ -61,6 +61,15 @@ function setupRules(req, next) {
61
61
  });
62
62
  }
63
63
 
64
+ function handleCodec(socket, enTrans, deTrans, handleError) {
65
+ enTrans.on('error', handleError);
66
+ deTrans.on('error', handleError);
67
+ deTrans.once('end', function() {
68
+ util.endSocket(socket);
69
+ });
70
+ enTrans.pipe(socket).pipe(deTrans);
71
+ }
72
+
64
73
  function getDecoder(obj) {
65
74
  return function (socket, callback) {
66
75
  var encoding = obj._originEncoding;
@@ -97,9 +106,7 @@ function getDecoder(obj) {
97
106
  delete obj.headers['content-length'];
98
107
  var enTrans = getEncodeTransform();
99
108
  var deTrans = getDecodeTransform();
100
- enTrans.pipe(socket).pipe(deTrans);
101
- enTrans.on('error', handleError);
102
- deTrans.on('error', handleError);
109
+ handleCodec(socket, enTrans, deTrans, handleError);
103
110
  if (decoder) {
104
111
  decoder.pipe(enTrans);
105
112
  } else {
@@ -127,9 +134,7 @@ function getEncoder(obj, req) {
127
134
  delete obj.headers['content-length'];
128
135
  var enTrans = getEncodeTransform();
129
136
  var deTrans = getDecodeTransform();
130
- enTrans.on('error', handleError);
131
- deTrans.on('error', handleError);
132
- enTrans.pipe(socket).pipe(deTrans);
137
+ handleCodec(socket, enTrans, deTrans, handleError);
133
138
  socket = enTrans;
134
139
  if (encoder) {
135
140
  deTrans.pipe(encoder);
@@ -36,7 +36,7 @@ module.exports = function (req, res) {
36
36
  }
37
37
  !util.isEnable(req, 'keepAllCSP') && util.disableCSP(_res.headers);
38
38
  !req._customCache && util.disableResStore(_res.headers);
39
- var name = util.getPath(util.rule.getMatcher(weinre));
39
+ var name = util.removeProtocol(weinre.matcher, true);
40
40
  var transform = new Transform();
41
41
  transform._transform = function (chunk, _, callback) {
42
42
  if (!chunk) {