whistle 2.10.10 → 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.
- package/bin/api.js +1 -83
- package/bin/whistle.js +5 -0
- package/biz/webui/cgi-bin/get-data.js +0 -1
- package/biz/webui/cgi-bin/init.js +0 -1
- package/biz/webui/cgi-bin/status.js +2 -1
- package/biz/webui/htdocs/index.html +1 -1
- package/biz/webui/htdocs/js/index.js +48 -48
- package/index.js +10 -12
- package/lib/config.js +15 -66
- package/lib/https/ca.js +1 -1
- package/lib/https/h2.js +1 -1
- package/lib/https/index.js +113 -97
- package/lib/inspectors/data.js +2 -0
- package/lib/inspectors/req.js +3 -6
- package/lib/inspectors/res.js +0 -3
- package/lib/plugins/index.js +1 -1
- package/lib/rules/index.js +2 -1
- package/lib/rules/protocols.js +2 -4
- package/lib/rules/rules.js +2 -4
- package/lib/service/compose-data.js +2 -1
- package/lib/service/composer.js +5 -7
- package/lib/socket-mgr.js +4 -1
- package/lib/tunnel.js +9 -5
- package/lib/util/common.js +142 -12
- package/lib/util/index.js +19 -46
- package/package.json +3 -3
package/lib/tunnel.js
CHANGED
|
@@ -206,10 +206,9 @@ function tunnelProxy(server, proxy, type) {
|
|
|
206
206
|
if (!isIPHost || !hasCustomCerts()) {
|
|
207
207
|
return false;
|
|
208
208
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return reqSocket.useProxifier;
|
|
209
|
+
var useProxifier = (config.proxifier2 || (config.proxifier && isWebPort)) && !disable.proxifier;
|
|
210
|
+
reqSocket.useProxifier = useProxifier;
|
|
211
|
+
return useProxifier;
|
|
213
212
|
};
|
|
214
213
|
var resolvedPlugin;
|
|
215
214
|
if (isIntercept()) {
|
|
@@ -248,14 +247,19 @@ function tunnelProxy(server, proxy, type) {
|
|
|
248
247
|
pluginMgr.getTunnelRules(req, function (_rulesMgr) {
|
|
249
248
|
handlePluginRules(_rulesMgr);
|
|
250
249
|
policy = headers[config.WHISTLE_POLICY_HEADER];
|
|
250
|
+
var useSniAsTunnelDomain = util.isEnable(req, 'useSniAsTunnelDomain');
|
|
251
|
+
var noCapture;
|
|
251
252
|
if (!reqSocket._hasError && !req._authForbidden &&
|
|
252
|
-
!req.fromComposer && (req._forceCapture || isIntercept())) {
|
|
253
|
+
!req.fromComposer && (req._forceCapture || isIntercept() || (noCapture = useSniAsTunnelDomain))) {
|
|
253
254
|
reqSocket.rulesHeaders = req.rulesHeaders;
|
|
254
255
|
reqSocket.enable = req.enable;
|
|
255
256
|
reqSocket.disable = req.disable;
|
|
256
257
|
reqSocket.tunnelHostname = hostname;
|
|
258
|
+
reqSocket.tunnelPort = options.port;
|
|
257
259
|
reqSocket.rules = _rules;
|
|
258
260
|
reqSocket._pluginVars = req._pluginVars;
|
|
261
|
+
reqSocket._noCaptureW2 = noCapture;
|
|
262
|
+
reqSocket._useSniAsTunnelDomain = useSniAsTunnelDomain;
|
|
259
263
|
dispatch(
|
|
260
264
|
reqSocket,
|
|
261
265
|
function (chunk) {
|
package/lib/util/common.js
CHANGED
|
@@ -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);
|
|
@@ -1905,16 +1912,14 @@ exports.connectInner = function (options, cb, config) {
|
|
|
1905
1912
|
auth = 'Basic ' + auth.toString('base64');
|
|
1906
1913
|
setRawHeader(headers, 'proxy-authorization', auth);
|
|
1907
1914
|
}
|
|
1915
|
+
var req;
|
|
1908
1916
|
var timer = setTimeout(function () {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
req.destroy();
|
|
1912
|
-
}
|
|
1917
|
+
req.emit('error', TIMEOUT_ERR);
|
|
1918
|
+
req.destroy();
|
|
1913
1919
|
}, CONN_TIMEOUT);
|
|
1914
|
-
var req;
|
|
1915
1920
|
var clearTimer = function (err) {
|
|
1916
1921
|
if (timer) {
|
|
1917
|
-
err && req
|
|
1922
|
+
err && req.destroy();
|
|
1918
1923
|
clearTimeout(timer);
|
|
1919
1924
|
timer = null;
|
|
1920
1925
|
}
|
|
@@ -1929,14 +1934,15 @@ exports.connectInner = function (options, cb, config) {
|
|
|
1929
1934
|
socket.on('error', function() {
|
|
1930
1935
|
socket.destroy();
|
|
1931
1936
|
});
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
'Tunneling socket could not be established, statusCode=' +
|
|
1935
|
-
res.statusCode
|
|
1936
|
-
);
|
|
1937
|
-
err.statusCode = res.statusCode;
|
|
1937
|
+
var status = res.statusCode;
|
|
1938
|
+
if (status !== 200) {
|
|
1938
1939
|
socket.destroy();
|
|
1939
1940
|
process.nextTick(function () {
|
|
1941
|
+
var err = new Error(
|
|
1942
|
+
'Tunneling socket could not be established, statusCode=' +
|
|
1943
|
+
status
|
|
1944
|
+
);
|
|
1945
|
+
err.statusCode = status;
|
|
1940
1946
|
req.emit('error', err);
|
|
1941
1947
|
});
|
|
1942
1948
|
return;
|
|
@@ -2135,3 +2141,127 @@ function getHttpMeta(f1, f2, f3, headers, rawHeaders) {
|
|
|
2135
2141
|
}
|
|
2136
2142
|
|
|
2137
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;
|
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 =
|
|
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:
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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.
|
|
475
|
+
exports.runInContext = function(script, context) {
|
|
484
476
|
try {
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
|
|
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
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
|
706
|
+
return null;
|
|
734
707
|
};
|
|
735
708
|
|
|
736
709
|
var isString = common.isString;
|
|
@@ -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
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whistle",
|
|
3
3
|
"description": "HTTP, HTTP2, HTTPS, Websocket debugging proxy",
|
|
4
|
-
"version": "2.10.
|
|
4
|
+
"version": "2.10.11",
|
|
5
5
|
"dataDirname": ".whistle",
|
|
6
6
|
"localUIHost": "local.whistlejs.com",
|
|
7
7
|
"port": 8899,
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"url": "https://github.com/avwo/whistle.git"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"adm-zip": "^0.6.
|
|
40
|
+
"adm-zip": "^0.6.1",
|
|
41
41
|
"async-limiter": "2.0.0",
|
|
42
42
|
"body-parser": "^1.20.6",
|
|
43
43
|
"colors": "1.1.2",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"docs": "vitepress build docs"
|
|
76
76
|
},
|
|
77
77
|
"engines": {
|
|
78
|
-
"node": ">=
|
|
78
|
+
"node": ">= 16.0.0"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"babel-core": "^6.7.6",
|