whistle 2.10.9 → 2.10.11

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 (47) hide show
  1. package/assets/js/log.js +4 -0
  2. package/bin/api.js +3 -1
  3. package/bin/util.js +1 -1
  4. package/bin/whistle.js +5 -0
  5. package/biz/webui/cgi-bin/get-data.js +0 -1
  6. package/biz/webui/cgi-bin/init.js +0 -1
  7. package/biz/webui/cgi-bin/status.js +2 -1
  8. package/biz/webui/cgi-bin/util.js +1 -1
  9. package/biz/webui/cgi-bin/values/add.js +15 -13
  10. package/biz/webui/htdocs/index.html +1 -1
  11. package/biz/webui/htdocs/js/index.js +50 -51
  12. package/biz/webui/lib/index.js +39 -30
  13. package/index.js +10 -12
  14. package/lib/config.js +25 -79
  15. package/lib/handlers/file-proxy.js +1 -1
  16. package/lib/https/ca.js +18 -17
  17. package/lib/https/h2.js +54 -35
  18. package/lib/https/index.js +124 -107
  19. package/lib/inspectors/data.js +9 -3
  20. package/lib/inspectors/log.js +14 -33
  21. package/lib/inspectors/req.js +3 -6
  22. package/lib/inspectors/res.js +2 -5
  23. package/lib/inspectors/rules.js +11 -6
  24. package/lib/inspectors/weinre.js +1 -1
  25. package/lib/plugins/index.js +45 -54
  26. package/lib/plugins/load-plugin.js +31 -40
  27. package/lib/rules/dns.js +1 -1
  28. package/lib/rules/index.js +3 -1
  29. package/lib/rules/protocols.js +2 -4
  30. package/lib/rules/rules.js +3 -5
  31. package/lib/rules/util.js +2 -7
  32. package/lib/service/compose-data.js +2 -1
  33. package/lib/service/composer.js +12 -10
  34. package/lib/service/service.js +3 -5
  35. package/lib/socket-mgr.js +5 -2
  36. package/lib/tunnel.js +9 -5
  37. package/lib/util/common.js +190 -37
  38. package/lib/util/data-server.js +5 -1
  39. package/lib/util/drain.js +5 -6
  40. package/lib/util/file-mgr.js +1 -1
  41. package/lib/util/http-mgr.js +6 -2
  42. package/lib/util/index.js +48 -69
  43. package/lib/util/speed-transform.js +2 -2
  44. package/lib/util/transproto.js +4 -3
  45. package/lib/util/whistle-transform.js +2 -2
  46. package/lib/util/zlib.js +1 -1
  47. package/package.json +4 -4
@@ -74,6 +74,13 @@ var PROP_SEP_RE = /(\\*)([|&]|\\[stnrfv])/g;
74
74
  var TEMP_FILE_RE = /^(?:\/?temp\/)?([\da-f]{64})(?:\.[\w.-]+)?$/;
75
75
  var TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
76
76
  var SAVED_SESSIONS_FILE_RE = /_([1-9]\d*)_(\d+)$/;
77
+ var REG_EXP_RE = /^\/(.+)\/(i?u?|ui)$/;
78
+
79
+ function isRegExp(regExp) {
80
+ return REG_EXP_RE.test(regExp);
81
+ }
82
+
83
+ exports.isRegExp = isRegExp;
77
84
 
78
85
  exports.getSavedFileItem = function(filename) {
79
86
  var result = SAVED_SESSIONS_FILE_RE.exec(filename);
@@ -469,13 +476,22 @@ exports.addTrailerNames = function (
469
476
  });
470
477
  };
471
478
 
472
- exports.onResEnd = function (res, callback) {
473
- var state = res._readableState || '';
474
- if (state.endEmitted) {
479
+ function onReadEnd(stream, callback) {
480
+ if (stream.readableEnded) {
475
481
  return callback();
476
482
  }
477
- res.on('end', callback);
478
- };
483
+ stream.once('end', callback);
484
+ }
485
+
486
+ function onWriteEnd(stream, callback) {
487
+ if (stream.writableFinished) {
488
+ return callback();
489
+ }
490
+ stream.once('finish', callback);
491
+ }
492
+
493
+ exports.onReadEnd = onReadEnd;
494
+ exports.onWriteEnd = onWriteEnd;
479
495
 
480
496
  var UPGRADE_RE = /^\s*upgrade\s*$/i;
481
497
  var WS_RE = /^\s*websocket\s*$/i;
@@ -725,7 +741,7 @@ function readStream(stream, callback) {
725
741
  stream.on('data', function(chunk) {
726
742
  buffer.push(chunk);
727
743
  });
728
- stream.once('end', function() {
744
+ onReadEnd(stream, function() {
729
745
  var buf;
730
746
  var text;
731
747
  var json;
@@ -899,12 +915,11 @@ exports.onSocketEnd = function(socket, callback) {
899
915
  callback = null;
900
916
  }
901
917
  };
902
- if (socket.aborted || socket.destroyed || socket._hasError) {
918
+ if (socket.aborted || socket.destroyed || socket.closed || socket._hasError) {
903
919
  return execCallback();
904
920
  }
905
921
  socket.on('error', execCallback);
906
922
  socket.once('close', execCallback);
907
- socket.once('end', execCallback);
908
923
  socket.once('timeout', execCallback);
909
924
  };
910
925
 
@@ -1579,6 +1594,14 @@ function safeEncodeURIComponent(ch) {
1579
1594
  return ch;
1580
1595
  }
1581
1596
 
1597
+ function safeDecodeURIComponent(str) {
1598
+ try {
1599
+ return decodeURIComponent(str);
1600
+ } catch (e) {}
1601
+
1602
+ return str;
1603
+ }
1604
+
1582
1605
  exports.safeEncodeURIComponent = safeEncodeURIComponent;
1583
1606
 
1584
1607
  /**
@@ -1889,36 +1912,46 @@ exports.connectInner = function (options, cb, config) {
1889
1912
  auth = 'Basic ' + auth.toString('base64');
1890
1913
  setRawHeader(headers, 'proxy-authorization', auth);
1891
1914
  }
1915
+ var req;
1892
1916
  var timer = setTimeout(function () {
1893
- if (req) {
1894
- req.emit('error', TIMEOUT_ERR);
1895
- req.destroy();
1896
- req.socket && req.socket.destroy();
1897
- }
1917
+ req.emit('error', TIMEOUT_ERR);
1918
+ req.destroy();
1898
1919
  }, CONN_TIMEOUT);
1899
- var req = httpModule.request(proxyOptions);
1900
- req.on('error', noop);
1901
- req
1902
- .on('connect', function (res, socket) {
1920
+ var clearTimer = function (err) {
1921
+ if (timer) {
1922
+ err && req.destroy();
1903
1923
  clearTimeout(timer);
1904
- socket.on('error', noop);
1905
- if (res.statusCode !== 200) {
1924
+ timer = null;
1925
+ }
1926
+ };
1927
+ req = httpModule.request(proxyOptions);
1928
+ req.on('error', clearTimer);
1929
+ req.on('connect', function (res, socket) {
1930
+ if (!timer) {
1931
+ return;
1932
+ }
1933
+ clearTimer();
1934
+ socket.on('error', function() {
1935
+ socket.destroy();
1936
+ });
1937
+ var status = res.statusCode;
1938
+ if (status !== 200) {
1939
+ socket.destroy();
1940
+ process.nextTick(function () {
1906
1941
  var err = new Error(
1907
1942
  'Tunneling socket could not be established, statusCode=' +
1908
- res.statusCode
1943
+ status
1909
1944
  );
1910
- err.statusCode = res.statusCode;
1911
- socket.destroy();
1912
- process.nextTick(function () {
1913
- req.emit('error', err);
1914
- });
1915
- return;
1916
- }
1917
- if (res.headers[ALLOW_ACK]) {
1918
- socket.write('1');
1919
- }
1920
- cb(socket, res);
1921
- })
1945
+ err.statusCode = status;
1946
+ req.emit('error', err);
1947
+ });
1948
+ return;
1949
+ }
1950
+ if (res.headers[ALLOW_ACK]) {
1951
+ socket.write('1');
1952
+ }
1953
+ cb(socket, res);
1954
+ })
1922
1955
  .end();
1923
1956
  return req;
1924
1957
  };
@@ -2052,11 +2085,7 @@ exports.getSessionInfo = function(headers) {
2052
2085
  var value = data[LAST_RAW_INDEX + index];
2053
2086
  if (value) {
2054
2087
  rawResult[key] = value;
2055
- try {
2056
- result[key] = decodeURIComponent(value);
2057
- } catch (e) {
2058
- result[key] = value;
2059
- }
2088
+ result[key] = safeDecodeURIComponent(value);
2060
2089
  }
2061
2090
  });
2062
2091
  result._ = rawResult;
@@ -2112,3 +2141,127 @@ function getHttpMeta(f1, f2, f3, headers, rawHeaders) {
2112
2141
  }
2113
2142
 
2114
2143
  exports.getHttpMeta = getHttpMeta;
2144
+
2145
+
2146
+ var PATTERN_FIELDS = ['url', 'm', 'method', 'reqH', 'reqHeader', 'reqHeaders',
2147
+ 'b', 'body', 'reqBody', 's', 'statusCode', 'status', 'resH', 'resHeader', 'resHeaders'];
2148
+
2149
+ function getHeaderKey(key) {
2150
+ return !key || typeof key !== 'string' || /[^\w!#$%&'*+.^`|~-]/.test(key) ? '' : key.toLowerCase();
2151
+ }
2152
+
2153
+ function createFilter(item, name, key) {
2154
+ var protocol = (item.exclude ? 'ex' : 'in') + 'cludeFilter://';
2155
+ name = name ? name + (key ? '.' : ':') : '';
2156
+ key = key ? key + ':' : '';
2157
+ return protocol + name + key + item.value;
2158
+ }
2159
+
2160
+ function wrapRegexp(value, item) {
2161
+ var flags = item.ignoreCase || item.matchCase === false ? 'i' : '';
2162
+ return { value: '/' + value.replace(/\//g, '\\$&') + '/' + flags, exclude: item.exclude};
2163
+ }
2164
+
2165
+ function formatPath(url) {
2166
+ if (/^\^?\/\//.test(url)) {
2167
+ return url;
2168
+ }
2169
+ return url.replace(/^(\^?(?:https?:\/\/|wss?:\/\/)?)([/?.])/, function(all, prefix, flag) {
2170
+ return flag === '/' ? prefix + '**/' : (prefix[0] === '^' ? prefix : '^' + prefix) + '**/**' + flag;
2171
+ });
2172
+ }
2173
+
2174
+ function formatUrlPattern(item) {
2175
+ var value = item.value;
2176
+ var type = item.type;
2177
+ var prefix;
2178
+ if (/^\s*(keyword|contains?)\s*$/i.test(type)) {
2179
+ return wrapRegexp(item.value.replace(/[|/\\{}()[\]^$+?*.]/g, '\\$&'), item);
2180
+ }
2181
+ if (/^\s*regexp?\s*$/i.test(type)) {
2182
+ return wrapRegexp(item.value.replace(/\//g, '\\$&'), item);
2183
+ }
2184
+ if (/^\s*(wildcard|star)\s*$/i.test(type)) {
2185
+ return { value: formatPath(item.value.replace(/^\^*/, '^')), exclude: item.exclude };
2186
+ }
2187
+ if (value[0] === '?') {
2188
+ prefix = '^**/**';
2189
+ } else if (value[0] !== '/' || value[1] === '/' || isRegExp(value)) {
2190
+ return { value: formatPath(value) };
2191
+ }
2192
+ return { value: (prefix || '**') + value };
2193
+ }
2194
+
2195
+ function createPattern(list) {
2196
+ if (!list) {
2197
+ return '';
2198
+ }
2199
+ if (!Array.isArray(list)) {
2200
+ list = [list];
2201
+ }
2202
+ var len = list.length;
2203
+ if (!len) {
2204
+ return '';
2205
+ }
2206
+ var result = [];
2207
+ var urlPattern;
2208
+ var key;
2209
+ var add = function(item, name, key) {
2210
+ var p = createFilter(item, name, key);
2211
+ if (result.indexOf(p) === -1) {
2212
+ result.push(p);
2213
+ }
2214
+ };
2215
+ list.forEach(function(item) {
2216
+ if (!item || !item.value) {
2217
+ return;
2218
+ }
2219
+ var name = item.name || 'url';
2220
+ if (PATTERN_FIELDS.indexOf(name) === -1) {
2221
+ return;
2222
+ }
2223
+ if (typeof item.value !== 'string') {
2224
+ item.value = String(item.value);
2225
+ }
2226
+ if (/\s/.test(item.value)) {
2227
+ return;
2228
+ }
2229
+ switch(name) {
2230
+ case 'url':
2231
+ item = formatUrlPattern(item);
2232
+ if (urlPattern || item.exclude) {
2233
+ add(item);
2234
+ } else {
2235
+ urlPattern = item.value;
2236
+ }
2237
+ return;
2238
+ case 'm':
2239
+ case 'method':
2240
+ return add(item, 'm');
2241
+ case 'reqH':
2242
+ case 'reqHeader':
2243
+ case 'reqHeaders':
2244
+ key = getHeaderKey(item.key);
2245
+ return key && add(item, 'reqH', key);
2246
+ case 'b':
2247
+ case 'body':
2248
+ case 'reqBody':
2249
+ return add(item, 'b');
2250
+ case 's':
2251
+ case 'statusCode':
2252
+ case 'status':
2253
+ return add(item, 's');
2254
+ case 'resH':
2255
+ case 'resHeader':
2256
+ case 'resHeaders':
2257
+ key = getHeaderKey(item.key);
2258
+ return key && add(item, 'resH', key);
2259
+ }
2260
+ });
2261
+ if (urlPattern || result.length) {
2262
+ result.unshift(urlPattern || '*');
2263
+ }
2264
+ return result.join(' ');
2265
+ }
2266
+
2267
+ exports.createPattern = createPattern;
@@ -323,7 +323,7 @@ function handleRequest(req, data) {
323
323
  delete data.abort;
324
324
  }
325
325
  };
326
- req.on('end', removeAbort);
326
+ util.onReadEnd(req, removeAbort);
327
327
  req.on('error', removeAbort);
328
328
  req.on('abort', removeAbort);
329
329
  data.version = version;
@@ -743,6 +743,10 @@ module.exports = function init(_proxy) {
743
743
  }
744
744
  }
745
745
 
746
+ if (filterType === 'SSE') {
747
+ return rawType === 'text/event-stream';
748
+ }
749
+
746
750
  var curType = util.getContentType(rawType);
747
751
  for (i = 0, len = TYPES.length; i < len; i++) {
748
752
  type = TYPES[i];
package/lib/util/drain.js CHANGED
@@ -1,14 +1,13 @@
1
- var createTransform = require('./common').createTransform;
1
+ var common = require('./common');
2
2
 
3
- var noop = function () {};
3
+ var noop = common.noop;
4
+ var onReadEnd = common.onReadEnd;
4
5
 
5
6
  module.exports = function (stream, endHandler) {
6
7
  if (stream._hasAlreadyDrain || (!stream.noReqBody && stream.useH2)) {
7
8
  return typeof endHandler == 'function' && endHandler();
8
9
  }
9
10
  stream._hasAlreadyDrain = true;
10
- var emitEndStream = createTransform();
11
- emitEndStream.on('data', noop).on('error', noop);
12
- emitEndStream.on('end', endHandler);
13
- stream.pipe(emitEndStream);
11
+ onReadEnd(stream, endHandler);
12
+ stream.on('data', noop).on('error', noop);
14
13
  };
@@ -58,7 +58,7 @@ function readSingleFile(path, callback) {
58
58
  }
59
59
  });
60
60
  stream.on('error', execCallback);
61
- stream.on('end', execCallback);
61
+ stream.once('end', execCallback);
62
62
  }
63
63
 
64
64
  function getFileMap(list) {
@@ -143,7 +143,11 @@ function sendReq(options, callback) {
143
143
 
144
144
  var timer, client;
145
145
  var handleCallback = function (err, res) {
146
+ if (timer == null) {
147
+ return;
148
+ }
146
149
  clearTimeout(timer);
150
+ timer = null;
147
151
  if (httpModule) {
148
152
  httpModule = null;
149
153
  callback(err, res);
@@ -249,7 +253,7 @@ function request(options, callback) {
249
253
  handleCallback(err, res, getBody(body, len));
250
254
  }
251
255
  });
252
- res.on('end', function() {
256
+ res.once('end', function() {
253
257
  handleCallback(null, res, getBody(body, len));
254
258
  });
255
259
  };
@@ -325,7 +329,7 @@ function readFile(url, callback) {
325
329
  }
326
330
  });
327
331
  stream.on('error', listener);
328
- stream.on('end', listener);
332
+ stream.once('end', listener);
329
333
  });
330
334
  }
331
335
 
package/lib/util/index.js CHANGED
@@ -24,7 +24,7 @@ var ReplacePatternTransform = require('./replace-pattern-transform');
24
24
  var common = require('./common');
25
25
  var proc = require('./process');
26
26
  var parseUrl = require('./parse-url');
27
- var h2Consts = config.enableH2 ? require('http2').constants : {};
27
+ var h2Consts = require('http2').constants;
28
28
 
29
29
  var protocols = protoMgr.protocols;
30
30
  var resProtocols = protoMgr.resProtocols;
@@ -39,7 +39,6 @@ var pendingFiles = {};
39
39
  var localIpCache = new LRU({ max: 120 });
40
40
  var LOCALHOST = '127.0.0.1';
41
41
  var aliasProtocols = protoMgr.aliasProtocols;
42
- var CONTEXT = vm.createContext();
43
42
  var END_SLASH_RE = /[/\\]$/;
44
43
  var GEN_URL_RE = /^\s*(?:https?:)?\/\/\w[^\s]*\s*$/i;
45
44
  var CORS_KEY_RE = /^(?:enable|use-credentials|useCredentials|credentials)$/i;
@@ -50,17 +49,12 @@ var STYLE_START = toBuffer('<style>');
50
49
  var STYLE_END = toBuffer('</style>');
51
50
  var PROXY_RE = /^x?(?:socks|https?-proxy|proxy|internal(?:-https)?-proxy)$/;
52
51
  var SEP_RE = /[|&]/;
53
- var ctxTimer;
54
52
  var END_RE = /[/\\]$/;
55
53
  var EXPIRED_SEC = -123456;
56
54
  var EXP_COOKIE = { maxAge: EXPIRED_SEC, path: '/' };
57
55
  var EXP_SECURE_COOKIE = { maxAge: EXPIRED_SEC, path: '/', secure: true };
58
56
  var EXP_COOKIE_D = { maxAge: EXPIRED_SEC, path: '/' };
59
57
  var EXP_SECURE_COOKIE_D = { maxAge: EXPIRED_SEC, path: '/', secure: true };
60
- var resetContext = function () {
61
- ctxTimer = null;
62
- CONTEXT = vm.createContext();
63
- };
64
58
  var TIMEOUT_ERR = common.TIMEOUT_ERR;
65
59
  var SUB_MATCH_RE = /\$[&\d]/;
66
60
  var PROTO_NAME_RE = /^([\w.-]+):\/\//;
@@ -89,6 +83,7 @@ var getMatcher = common.getMatcher;
89
83
  var getMatcherValue = common.getMatcherValue;
90
84
  var getRemoteAddr = common.getRemoteAddr;
91
85
  var getRemotePort = common.getRemotePort;
86
+ var isRegExp = common.isRegExp;
92
87
  var workerIndex = process.env && process.env.workerIndex;
93
88
  var INTERNAL_ID = Date.now().toString(16) + Math.floor(Math.random() * 10000000).toString(16);
94
89
  var pluginMgr;
@@ -175,6 +170,7 @@ exports.PLUGIN_INSPECTOR_CONFIG = Symbol('inspectorConfig');
175
170
  exports.drain = require('./drain');
176
171
  exports.isWin = process.platform === 'win32';
177
172
  exports.isUtf8 = isUtf8;
173
+ exports.isRegExp = isRegExp;
178
174
  exports.WhistleTransform = require('./whistle-transform');
179
175
  exports.ReplacePatternTransform = ReplacePatternTransform;
180
176
  exports.replacePattern = replacePattern;
@@ -425,7 +421,7 @@ exports.compare = compare;
425
421
  var scriptCache = {};
426
422
  var VM_OPTIONS = {
427
423
  displayErrors: false,
428
- timeout: 60
424
+ timeout: 72
429
425
  };
430
426
  var MAX_SCRIPT_SIZE = 1024 * 256;
431
427
  var MAX_SCRIPT_CACHE_COUNT = 64;
@@ -471,34 +467,20 @@ function getScript(content) {
471
467
  return script.script;
472
468
  }
473
469
 
474
- function clearContext() {
475
- Object.keys(CONTEXT).forEach(function (key) {
476
- delete CONTEXT[key];
477
- });
478
- if (!ctxTimer) {
479
- ctxTimer = setTimeout(resetContext, 30000);
480
- }
481
- }
470
+ var customConsole = {};
471
+ ['fatal', 'error', 'warn', 'info', 'log', 'debug'].forEach(function (level) {
472
+ customConsole[level] = logger[level];
473
+ });
482
474
 
483
- exports.execScriptSync = function(script, context) {
475
+ exports.runInContext = function(script, context) {
484
476
  try {
485
- if ((script = getScript(script))) {
486
- CONTEXT.console = {};
487
- ['fatal', 'error', 'warn', 'info', 'log', 'debug'].forEach(function (
488
- level
489
- ) {
490
- CONTEXT.console[level] = logger[level];
491
- });
492
- Object.keys(context).forEach(function (key) {
493
- CONTEXT[key] = context[key];
494
- });
495
- script.runInContext(CONTEXT, VM_OPTIONS);
477
+ if (script = getScript(script)) {
478
+ context = Object.assign({ console: Object.create(customConsole) }, context);
479
+ script.runInNewContext(context, VM_OPTIONS);
496
480
  }
497
481
  return true;
498
482
  } catch (e) {
499
483
  logger.error(e);
500
- } finally {
501
- clearContext();
502
484
  }
503
485
  };
504
486
 
@@ -603,14 +585,6 @@ function formatDate(now) {
603
585
 
604
586
  exports.formatDate = formatDate;
605
587
 
606
- var REG_EXP_RE = /^\/(.+)\/(i?u?|ui)$/;
607
-
608
- function isRegExp(regExp) {
609
- return REG_EXP_RE.test(regExp);
610
- }
611
-
612
- exports.isRegExp = isRegExp;
613
-
614
588
  var ORIG_REG_EXP = /^\/(.+)\/([igmu]{0,4})$/;
615
589
  var FLAGS_RE = /[igmu]{2}/;
616
590
 
@@ -723,14 +697,13 @@ function isAuthCapture(req) {
723
697
 
724
698
  exports.isAuthCapture = isAuthCapture;
725
699
 
726
- exports.toRegExp = function toRegExp(regExp, ignoreCase) {
727
- regExp = REG_EXP_RE.test(regExp);
728
- try {
729
- regExp = regExp && new RegExp(RegExp.$1, ignoreCase ? 'i' : RegExp.$2);
730
- } catch (e) {
731
- regExp = null;
700
+ exports.toRegExp = function(regExp, ignoreCase) {
701
+ if (isRegExp(regExp)) {
702
+ try {
703
+ return new RegExp(RegExp.$1, ignoreCase ? 'i' : RegExp.$2);
704
+ } catch (e) {}
732
705
  }
733
- return regExp;
706
+ return null;
734
707
  };
735
708
 
736
709
  var isString = common.isString;
@@ -967,8 +940,6 @@ function getPath(url, noProtocol) {
967
940
  return url;
968
941
  }
969
942
 
970
- exports.getPath = getPath;
971
-
972
943
  function getFilename(url) {
973
944
  if (typeof url == 'string' && (url = getPath(url).trim())) {
974
945
  var index = url.lastIndexOf('/');
@@ -1489,48 +1460,47 @@ function getRawType(data) {
1489
1460
 
1490
1461
  exports.getRawType = getRawType;
1491
1462
 
1492
- function getContentType(contentType) {
1493
- if (contentType && typeof contentType != 'string') {
1494
- contentType = contentType['content-type'] || contentType.contentType;
1463
+ function getContentType(type) {
1464
+ if (type && typeof type != 'string') {
1465
+ type = type['content-type'] || type.contentType;
1495
1466
  }
1496
- contentType = _getRawType(contentType);
1497
- if (!contentType) {
1467
+ type = _getRawType(type);
1468
+ if (!type) {
1498
1469
  return;
1499
1470
  }
1500
- if (contentType.indexOf('javascript') != -1) {
1471
+ if (type.indexOf('image/') != -1) {
1472
+ return 'IMG';
1473
+ }
1474
+ if (type.indexOf('javascript') != -1) {
1501
1475
  return 'JS';
1502
1476
  }
1503
1477
 
1504
- if (contentType.indexOf('css') != -1) {
1478
+ if (type.indexOf('css') != -1) {
1505
1479
  return 'CSS';
1506
1480
  }
1507
1481
 
1508
- if (contentType.indexOf('html') != -1) {
1482
+ if (type.indexOf('html') != -1) {
1509
1483
  return 'HTML';
1510
1484
  }
1511
1485
 
1512
- if (contentType.indexOf('json') != -1) {
1486
+ if (type.indexOf('json') != -1) {
1513
1487
  return 'JSON';
1514
1488
  }
1515
1489
 
1516
- if (contentType.indexOf('xml') != -1) {
1490
+ if (type.indexOf('xml') != -1) {
1517
1491
  return 'XML';
1518
1492
  }
1519
1493
 
1520
- if (contentType.indexOf('text/') != -1) {
1494
+ if (type.indexOf('text/') != -1) {
1521
1495
  return 'TEXT';
1522
1496
  }
1523
-
1524
- if (contentType.indexOf('image/') != -1) {
1525
- return 'IMG';
1526
- }
1527
1497
  }
1528
1498
 
1529
1499
  exports.getContentType = getContentType;
1530
1500
 
1531
- function isText(contentType) {
1532
- contentType = contentType && getContentType(contentType);
1533
- return contentType && contentType !== 'IMG';
1501
+ function isText(type) {
1502
+ type = type && getContentType(type);
1503
+ return type && type !== 'IMG';
1534
1504
  }
1535
1505
 
1536
1506
  exports.isText = isText;
@@ -1654,7 +1624,7 @@ function getPipeIconvStream(headers) {
1654
1624
  };
1655
1625
 
1656
1626
  res.on('data', resolveCharset);
1657
- res.on('end', resolveCharset);
1627
+ onReadEnd(res, resolveCharset);
1658
1628
  });
1659
1629
 
1660
1630
  pipeStream.addTail(function (src, next) {
@@ -2649,7 +2619,10 @@ exports.getReqId = function (now) {
2649
2619
  };
2650
2620
 
2651
2621
  exports.onSocketEnd = common.onSocketEnd;
2652
- exports.onResEnd = common.onResEnd;
2622
+
2623
+ var onReadEnd = common.onReadEnd;
2624
+ exports.onReadEnd = onReadEnd;
2625
+ exports.onWriteEnd = common.onWriteEnd;
2653
2626
 
2654
2627
  exports.getEmptyRes = function getRes() {
2655
2628
  var res = common.createTransform();
@@ -2983,7 +2956,7 @@ exports.disableReqProps = function (req) {
2983
2956
  delete headers['user-agent'];
2984
2957
  }
2985
2958
 
2986
- if (disable.gzip || isEnable(req, 'captureStream')) {
2959
+ if (disable.gzip || disable.zip || isEnable(req, 'captureStream')) {
2987
2960
  delete headers['accept-encoding'];
2988
2961
  }
2989
2962
 
@@ -3623,7 +3596,7 @@ function readOneChunk(stream, callback, timeout) {
3623
3596
  timer = setTimeout(handler, timeout);
3624
3597
  }
3625
3598
  stream.on('data', handler);
3626
- stream.on('end', handler);
3599
+ onReadEnd(stream, handler);
3627
3600
  }
3628
3601
 
3629
3602
  exports.readOneChunk = readOneChunk;
@@ -4031,3 +4004,9 @@ exports.joinBuffer = function(buf, chunk) {
4031
4004
  exports.formatBuffer = function(buf, len) {
4032
4005
  return Array.isArray(buf) ? Buffer.concat(buf, len) : buf;
4033
4006
  };
4007
+
4008
+ exports.endSocket = function (socket) {
4009
+ if (!socket.destroyed && !socket.writableEnded) {
4010
+ socket.end();
4011
+ }
4012
+ };
@@ -6,11 +6,11 @@ function SpeedTransform(options, req) {
6
6
  Transform.call(this);
7
7
  options = options || {};
8
8
  this._req = req;
9
- var value = parseInt((options.speed * 1000) / 8);
9
+ var value = parseInt((options.speed * 1000) / 8, 10);
10
10
  if (value > 0) {
11
11
  this._speed = value;
12
12
  }
13
- if ((value = parseInt(options.delay)) > 0) {
13
+ if ((value = parseInt(options.delay, 10)) > 0) {
14
14
  this._delay = value;
15
15
  }
16
16
  }
@@ -71,15 +71,15 @@ function Parser() {
71
71
  }
72
72
  var data = parseChunk();
73
73
  if (data === false) {
74
- return self.onEnd && self.onEnd();
74
+ return self.onEnd();
75
75
  }
76
76
  if (!data) {
77
77
  if (chunk === false) {
78
78
  return;
79
79
  }
80
- return self.onContinue && self.onContinue();
80
+ return self.onContinue();
81
81
  }
82
- self.onData && self.onData(data);
82
+ self.onData(data);
83
83
  self.write(false);
84
84
  };
85
85
  }
@@ -91,6 +91,7 @@ exports.getDecodeTransform = function () {
91
91
  parser.onEnd = function () {
92
92
  data && trans.push(data);
93
93
  trans.push(null);
94
+ data = null;
94
95
  };
95
96
  parser.onContinue = function () {
96
97
  if (transCb) {
@@ -9,11 +9,11 @@ var DOCTYPE = Buffer.from('<!DOCTYPE html>\r\n');
9
9
  function WhistleTransform(options, req) {
10
10
  Transform.call(this);
11
11
  options = options || {};
12
- var value = parseInt((options.speed * 1000) / 8);
12
+ var value = parseInt((options.speed * 1000) / 8, 10);
13
13
  if (value > 0) {
14
14
  this._speed = value;
15
15
  }
16
- if ((value = parseInt(options.delay)) > 0) {
16
+ if ((value = parseInt(options.delay, 10)) > 0) {
17
17
  this._delay = value;
18
18
  }
19
19
  this._noDoctype = options.noDoctype;