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.
- package/assets/menu.html +2 -0
- package/assets/modal.html +2 -0
- package/assets/tab.html +3 -3
- package/bin/proxy.js +2 -2
- package/bin/whistle.js +5 -5
- package/biz/webui/cgi-bin/add-rules-values.js +1 -1
- package/biz/webui/cgi-bin/check-update.js +0 -1
- package/biz/webui/cgi-bin/do-not-show-again.js +1 -1
- package/biz/webui/cgi-bin/download.js +1 -1
- package/biz/webui/cgi-bin/enable-http2.js +1 -1
- package/biz/webui/cgi-bin/get-matched-rules.js +14 -0
- package/biz/webui/cgi-bin/hide-https-connects.js +1 -1
- package/biz/webui/cgi-bin/intercept-https-connects.js +1 -1
- package/biz/webui/cgi-bin/plugins/disable-all-plugins.js +1 -1
- package/biz/webui/cgi-bin/reset-local-address.js +1 -1
- package/biz/webui/cgi-bin/rules/allow-multiple-choice.js +1 -1
- package/biz/webui/cgi-bin/rules/clear-dns-cache.js +7 -0
- package/biz/webui/cgi-bin/rules/disable-all-rules.js +1 -1
- package/biz/webui/cgi-bin/rules/disable-default.js +1 -1
- package/biz/webui/cgi-bin/rules/enable-back-rules-first.js +1 -1
- package/biz/webui/cgi-bin/rules/enable-default.js +1 -1
- package/biz/webui/cgi-bin/rules/move-to.js +1 -1
- package/biz/webui/cgi-bin/rules/remove.js +1 -1
- package/biz/webui/cgi-bin/rules/rename.js +1 -1
- package/biz/webui/cgi-bin/rules/select.js +1 -1
- package/biz/webui/cgi-bin/rules/set-sys-hosts.js +1 -1
- package/biz/webui/cgi-bin/rules/unselect.js +1 -1
- package/biz/webui/cgi-bin/values/move-to.js +1 -1
- package/biz/webui/cgi-bin/values/remove.js +1 -1
- package/biz/webui/cgi-bin/values/rename.js +1 -1
- package/biz/webui/htdocs/index.html +1 -1
- package/biz/webui/htdocs/js/index.js +54 -52
- package/biz/webui/lib/index.js +32 -2
- package/lib/config.js +4 -4
- package/lib/handlers/file-proxy.js +1 -1
- package/lib/https/ca.js +31 -0
- package/lib/https/index.js +4 -2
- package/lib/index.js +0 -2
- package/lib/inspectors/data.js +120 -13
- package/lib/inspectors/req.js +5 -3
- package/lib/plugins/get-plugins-sync.js +15 -11
- package/lib/plugins/get-plugins.js +11 -12
- package/lib/plugins/load-plugin.js +1 -1
- package/lib/plugins/module-paths.js +4 -0
- package/lib/plugins/util.js +8 -0
- package/lib/rules/dns.js +4 -0
- package/lib/rules/rules.js +7 -8
- package/lib/service/composer.js +86 -39
- package/lib/service/service.js +14 -10
- package/lib/service/util.js +3 -42
- package/lib/socket-mgr.js +2 -16
- package/lib/tunnel.js +3 -0
- package/lib/upgrade.js +1 -0
- package/lib/util/common.js +54 -10
- package/lib/util/data-server.js +10 -0
- package/lib/util/index.js +38 -19
- package/package.json +2 -2
- package/biz/webui/cgi-bin/import-remote.js +0 -37
package/lib/rules/rules.js
CHANGED
|
@@ -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(
|
|
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() <
|
|
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);
|
package/lib/service/composer.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
354
|
+
var reqBody = req.body;
|
|
355
|
+
var fullUrl = reqBody.url;
|
|
337
356
|
if (!fullUrl || typeof fullUrl !== 'string') {
|
|
338
|
-
return res.json({ec:
|
|
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:
|
|
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(
|
|
353
|
-
var method = common.getMethod(
|
|
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
|
-
|
|
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 =
|
|
369
|
-
|
|
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
|
-
|
|
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 (!
|
|
389
|
-
handleComposerHistory(
|
|
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 =
|
|
398
|
-
var body = base64 ||
|
|
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 &&
|
|
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 ||
|
|
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,
|
|
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 =
|
|
457
|
-
count = count > 0 ? count :
|
|
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,
|
|
511
|
+
handleHttp(options, handleResponse, count, reqBody.reqId, testId);
|
|
465
512
|
}
|
|
466
513
|
if (!handleResponse) {
|
|
467
|
-
res.json({ec: 0
|
|
514
|
+
res.json({ec: 0});
|
|
468
515
|
}
|
|
469
516
|
});
|
|
470
517
|
};
|
package/lib/service/service.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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);
|
package/lib/service/util.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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;
|
package/lib/util/common.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
};
|
package/lib/util/data-server.js
CHANGED
|
@@ -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();
|