whistle 2.10.6 → 2.10.7
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 -2
- package/assets/modal.html +2 -2
- package/assets/tab.html +2 -2
- package/bin/api.js +545 -0
- package/bin/import.js +3 -3
- package/bin/plugin.js +4 -8
- package/bin/use.js +22 -8
- package/bin/util.js +10 -5
- package/bin/whistle.js +11 -1
- package/biz/webui/cgi-bin/abort.js +14 -5
- package/biz/webui/cgi-bin/frames.js +12 -0
- package/biz/webui/cgi-bin/is-enabled-https.js +6 -0
- package/biz/webui/cgi-bin/plugins/disable-plugin.js +16 -7
- package/biz/webui/cgi-bin/plugins/list.js +17 -0
- package/biz/webui/cgi-bin/plugins/plugin.js +21 -0
- package/biz/webui/cgi-bin/plugins/status.js +21 -0
- package/biz/webui/cgi-bin/rules/is-multi-select.js +7 -0
- package/biz/webui/cgi-bin/rules/move-top.js +11 -0
- package/biz/webui/cgi-bin/rules/select.js +1 -1
- package/biz/webui/cgi-bin/rules/select2.js +16 -0
- package/biz/webui/cgi-bin/rules/status.js +6 -0
- package/biz/webui/cgi-bin/rules/unselect.js +3 -2
- package/biz/webui/cgi-bin/rules/unselect2.js +18 -0
- package/biz/webui/cgi-bin/rules/value.js +19 -0
- package/biz/webui/cgi-bin/sessions.js +6 -0
- package/biz/webui/cgi-bin/util.js +14 -0
- package/biz/webui/htdocs/index.html +1 -1
- package/biz/webui/htdocs/js/index.js +32 -32
- package/biz/webui/lib/index.js +1 -3
- package/lib/https/index.js +4 -4
- package/lib/init.js +1 -0
- package/lib/inspectors/data.js +6 -4
- package/lib/inspectors/req.js +1 -2
- package/lib/inspectors/res.js +6 -10
- package/lib/plugins/index.js +2 -2
- package/lib/plugins/load-plugin.js +17 -6
- package/lib/rules/util.js +20 -10
- package/lib/service/composer.js +7 -22
- package/lib/service/data-center.js +0 -12
- package/lib/service/generate-saz.js +2 -2
- package/lib/service/service.js +6 -5
- package/lib/service/util.js +6 -16
- package/lib/socket-mgr.js +23 -17
- package/lib/tunnel.js +7 -17
- package/lib/upgrade.js +4 -8
- package/lib/util/common.js +75 -9
- package/lib/util/data-server.js +273 -0
- package/lib/util/index.js +8 -22
- package/lib/util/patch.js +2 -6
- package/lib/util/speed-transform.js +17 -6
- package/lib/util/whistle-transform.js +18 -7
- package/package.json +10 -7
package/lib/tunnel.js
CHANGED
|
@@ -23,7 +23,6 @@ var X_RE = /^x/;
|
|
|
23
23
|
var STATUS_CODES = require('http').STATUS_CODES || {};
|
|
24
24
|
var getRawHeaderNames = hparser.getRawHeaderNames;
|
|
25
25
|
var formatHeaders = hparser.formatHeaders;
|
|
26
|
-
var getRawHeaders = hparser.getRawHeaders;
|
|
27
26
|
var CRONET_RE = /\bCronet\b/;
|
|
28
27
|
var SYS_HOST_RE = /^(?:[^/:]+\.)?(?:apple|cdn-apple|icloud|office|office365|mzstatic|apple-cloudkit|microsoft|parallels)\.(?:com|cn|com\.cn)$/;
|
|
29
28
|
var TUNNEL_RE = /^tunnel:\/\//;
|
|
@@ -104,6 +103,7 @@ function tunnelProxy(server, proxy, type) {
|
|
|
104
103
|
testId: req._testId,
|
|
105
104
|
startTime: now,
|
|
106
105
|
dnsTime: now,
|
|
106
|
+
servername: reqSocket.useSNI === hostname ? undefined : reqSocket.useSNI,
|
|
107
107
|
captureError: true,
|
|
108
108
|
rules: req.rules,
|
|
109
109
|
req: {
|
|
@@ -382,7 +382,7 @@ function tunnelProxy(server, proxy, type) {
|
|
|
382
382
|
var reqDelay = util.getMatcherValue(_rules.reqDelay);
|
|
383
383
|
data.requestTime = data.dnsTime = Date.now();
|
|
384
384
|
if (reqDelay > 0) {
|
|
385
|
-
setTimeout(handleConnect, reqDelay);
|
|
385
|
+
util.setDelayTimer(reqSocket, setTimeout(handleConnect, reqDelay));
|
|
386
386
|
} else {
|
|
387
387
|
handleConnect();
|
|
388
388
|
}
|
|
@@ -404,8 +404,7 @@ function tunnelProxy(server, proxy, type) {
|
|
|
404
404
|
var proxyUrl;
|
|
405
405
|
if (tunnelPort) {
|
|
406
406
|
proxyUrl = 'proxy://127.0.0.1:' + tunnelPort;
|
|
407
|
-
reqSocket.customParser = req.customParser =
|
|
408
|
-
util.getParserStatus(req);
|
|
407
|
+
reqSocket.customParser = req.customParser = util.getParserStatus(req);
|
|
409
408
|
pluginMgr.addSessionInfo(req, _rules);
|
|
410
409
|
headers[config.PLUGIN_HOOK_NAME_HEADER] =
|
|
411
410
|
config.PLUGIN_HOOKS.TUNNEL;
|
|
@@ -553,7 +552,7 @@ function tunnelProxy(server, proxy, type) {
|
|
|
553
552
|
}
|
|
554
553
|
};
|
|
555
554
|
if (reqDelay > 0) {
|
|
556
|
-
setTimeout(connectProxy, reqDelay);
|
|
555
|
+
util.setDelayTimer(reqSocket, setTimeout(connectProxy, reqDelay));
|
|
557
556
|
} else {
|
|
558
557
|
connectProxy();
|
|
559
558
|
}
|
|
@@ -687,7 +686,7 @@ function tunnelProxy(server, proxy, type) {
|
|
|
687
686
|
};
|
|
688
687
|
var resDelay = util.getMatcherValue(_rules.resDelay);
|
|
689
688
|
if (resDelay > 0) {
|
|
690
|
-
setTimeout(handleEstablished, resDelay);
|
|
689
|
+
util.setDelayTimer(reqSocket, setTimeout(handleEstablished, resDelay));
|
|
691
690
|
} else {
|
|
692
691
|
handleEstablished();
|
|
693
692
|
}
|
|
@@ -770,23 +769,14 @@ function tunnelProxy(server, proxy, type) {
|
|
|
770
769
|
util.deleteResHeaders(req, resHeaders);
|
|
771
770
|
code = util.getMatcherValue(reqRules.replaceStatus) || code;
|
|
772
771
|
var isSuccess = code == 200;
|
|
773
|
-
var message =
|
|
774
|
-
isSuccess
|
|
775
|
-
? 'Connection Established'
|
|
776
|
-
: STATUS_CODES[code] || 'unknown';
|
|
777
|
-
var statusLine = ['HTTP/1.1', code, message].join(' ');
|
|
772
|
+
var message = isSuccess ? 'Connection Established' : STATUS_CODES[code] || 'unknown';
|
|
778
773
|
var curHeaders = resHeaders;
|
|
779
|
-
var rawData = statusLine;
|
|
780
774
|
if (req.fromComposer) {
|
|
781
775
|
curHeaders = extend({}, resHeaders);
|
|
782
776
|
curHeaders['x-whistle-req-id'] = req.reqId;
|
|
783
777
|
util.setFramesMode(curHeaders, isSuccess && inspect);
|
|
784
778
|
}
|
|
785
|
-
|
|
786
|
-
if (curHeaders) {
|
|
787
|
-
rawData += '\r\n' + curHeaders;
|
|
788
|
-
}
|
|
789
|
-
rawData += '\r\n\r\n';
|
|
779
|
+
var rawData = common.getHttpMeta('HTTP/1.1', code, message, curHeaders, rawHeaderNames);
|
|
790
780
|
try {
|
|
791
781
|
if (code != 200) {
|
|
792
782
|
reqSocket.end(rawData, cb);
|
package/lib/upgrade.js
CHANGED
|
@@ -6,9 +6,7 @@ var util = require('./util');
|
|
|
6
6
|
var config = require('./config');
|
|
7
7
|
var common = require('./util/common');
|
|
8
8
|
|
|
9
|
-
var formatHeaders = hparser.formatHeaders;
|
|
10
9
|
var getRawHeaderNames = hparser.getRawHeaderNames;
|
|
11
|
-
var getRawHeaders = hparser.getRawHeaders;
|
|
12
10
|
var PLUGIN_PATH_RE =
|
|
13
11
|
/^\/(\.\.\.whistle-path\.5b6af7b9884e1165\.\.\.\/\/\/)?(whistle|plugin)\.([^/?#]+)\/?/;
|
|
14
12
|
|
|
@@ -105,12 +103,10 @@ function upgradeHandler(req, socket) {
|
|
|
105
103
|
util.addTunnelData(socket, headers);
|
|
106
104
|
socket._clientId = util.getComposerClientId(headers);
|
|
107
105
|
var getBuffer = function (method, newHeaders, path) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
return Buffer.from(rawData + '\r\n\r\n');
|
|
106
|
+
method = method || 'GET';
|
|
107
|
+
path = path || socket.url || req.url;
|
|
108
|
+
newHeaders = newHeaders || headers;
|
|
109
|
+
return Buffer.from(common.getHttpMeta(method, path, 'HTTP/1.1', newHeaders, req.rawHeaders));
|
|
114
110
|
};
|
|
115
111
|
var sniPlugin = headers[util.SNI_PLUGIN_HEADER];
|
|
116
112
|
if (sniPlugin) {
|
package/lib/util/common.js
CHANGED
|
@@ -12,6 +12,7 @@ var json5 = require('json5');
|
|
|
12
12
|
var extend = require('extend');
|
|
13
13
|
var tls = require('tls');
|
|
14
14
|
var https = require('https');
|
|
15
|
+
var hparser = require('hparser');
|
|
15
16
|
var zlibx = require('./zlib');
|
|
16
17
|
var pkgConf = require('../../package.json');
|
|
17
18
|
var isUtf8 = require('./is-utf8');
|
|
@@ -72,10 +73,22 @@ var TOKEN = '\r\u0000\n\u0003\r';
|
|
|
72
73
|
var PROP_SEP_RE = /(\\*)([|&]|\\[stnrfv])/g;
|
|
73
74
|
var TEMP_FILE_RE = /^(?:\/?temp\/)?([\da-f]{64})(?:\.[\w.-]+)?$/;
|
|
74
75
|
var TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
|
|
76
|
+
var SAVED_SESSIONS_FILE_RE = /_([1-9]\d*)_(\d+)$/;
|
|
77
|
+
|
|
78
|
+
exports.getSavedFileItem = function(filename) {
|
|
79
|
+
var result = SAVED_SESSIONS_FILE_RE.exec(filename);
|
|
80
|
+
return result && {
|
|
81
|
+
count: +result[1],
|
|
82
|
+
time: +result[2],
|
|
83
|
+
filename: filename.slice(0, - result[1].length - result[2].length - 2)
|
|
84
|
+
};
|
|
85
|
+
};
|
|
75
86
|
|
|
76
87
|
|
|
77
88
|
exports.WHISTLE_UID_HEADER = '_x-whistle-uid_';
|
|
78
89
|
|
|
90
|
+
exports.isUtf8 = isUtf8;
|
|
91
|
+
|
|
79
92
|
exports.getTempFile = function(str) {
|
|
80
93
|
var match = TEMP_FILE_RE.exec(str);
|
|
81
94
|
return match && match[1];
|
|
@@ -670,18 +683,29 @@ exports.isGroup = function(name) {
|
|
|
670
683
|
};
|
|
671
684
|
|
|
672
685
|
var UTF8_RE = /^utf-?8$/i;
|
|
686
|
+
|
|
687
|
+
function getUtf8Buf(buffer, charset) {
|
|
688
|
+
charset = isString(charset) ? charset.trim() : '';
|
|
689
|
+
if (UTF8_RE.test(charset) || isUtf8(buffer)) {
|
|
690
|
+
return buffer;
|
|
691
|
+
}
|
|
692
|
+
try {
|
|
693
|
+
buffer = iconv.encode(buffer, charset || 'GB18030');
|
|
694
|
+
} catch (e) {}
|
|
695
|
+
return buffer;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
exports.getUtf8Buf = getUtf8Buf;
|
|
699
|
+
|
|
700
|
+
|
|
673
701
|
function bufferToString(buffer, encoding) {
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
} else {
|
|
677
|
-
encoding = null;
|
|
702
|
+
if (!buffer) {
|
|
703
|
+
return '';
|
|
678
704
|
}
|
|
679
|
-
if (Buffer.isBuffer(buffer)
|
|
680
|
-
|
|
681
|
-
buffer = iconv.encode(buffer, encoding || 'GB18030');
|
|
682
|
-
} catch (e) {}
|
|
705
|
+
if (Buffer.isBuffer(buffer)) {
|
|
706
|
+
buffer = getUtf8Buf(buffer, encoding);
|
|
683
707
|
}
|
|
684
|
-
return String(buffer
|
|
708
|
+
return String(buffer);
|
|
685
709
|
}
|
|
686
710
|
|
|
687
711
|
exports.bufferToString = bufferToString;
|
|
@@ -852,9 +876,24 @@ exports.getHostPort = function(str) {
|
|
|
852
876
|
}
|
|
853
877
|
};
|
|
854
878
|
|
|
879
|
+
function clearDelayTimer(socket) {
|
|
880
|
+
var timer = socket._w2Timer_;
|
|
881
|
+
if (timer) {
|
|
882
|
+
clearTimeout(timer);
|
|
883
|
+
socket._w2Timer_ = null;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
exports.clearDelayTimer = clearDelayTimer;
|
|
888
|
+
exports.setDelayTimer = function (socket, id) {
|
|
889
|
+
socket._w2Timer_ = id;
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
|
|
855
893
|
exports.onSocketEnd = function(socket, callback) {
|
|
856
894
|
var execCallback = function (err) {
|
|
857
895
|
socket._hasError = true;
|
|
896
|
+
clearDelayTimer(socket);
|
|
858
897
|
if (callback) {
|
|
859
898
|
callback(err);
|
|
860
899
|
callback = null;
|
|
@@ -970,6 +1009,10 @@ function isString(str) {
|
|
|
970
1009
|
|
|
971
1010
|
exports.isString = isString;
|
|
972
1011
|
|
|
1012
|
+
exports.isStr = function(str) {
|
|
1013
|
+
return typeof str === 'string';
|
|
1014
|
+
};
|
|
1015
|
+
|
|
973
1016
|
function isSafeNumStr(str) {
|
|
974
1017
|
if (str == '0') {
|
|
975
1018
|
return true;
|
|
@@ -1429,6 +1472,18 @@ exports.sendGzip = function (req, res, data) {
|
|
|
1429
1472
|
});
|
|
1430
1473
|
};
|
|
1431
1474
|
|
|
1475
|
+
exports.toString = function (str) {
|
|
1476
|
+
if (str != null) {
|
|
1477
|
+
if (typeof str === 'string') {
|
|
1478
|
+
return str;
|
|
1479
|
+
}
|
|
1480
|
+
try {
|
|
1481
|
+
return JSON.stringify(str) || '';
|
|
1482
|
+
} catch (e) {}
|
|
1483
|
+
}
|
|
1484
|
+
return '';
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1432
1487
|
exports.sendGzipText = function(req, res, headers, text, gzipText) {
|
|
1433
1488
|
if (!text || !canGzip(req) || (!gzipText && text.length < GZIP_THRESHOLD)) {
|
|
1434
1489
|
headers && res.writeHead(200, headers);
|
|
@@ -2046,3 +2101,14 @@ var COMMENT_RE = /#[^\r\n]*/g;
|
|
|
2046
2101
|
exports.removeComment = function(text) {
|
|
2047
2102
|
return text.replace(COMMENT_RE, '').trim();
|
|
2048
2103
|
};
|
|
2104
|
+
|
|
2105
|
+
function getHttpMeta(f1, f2, f3, headers, rawHeaders) {
|
|
2106
|
+
var statusLine = [f1, f2, f3].join(' ');
|
|
2107
|
+
if (headers) {
|
|
2108
|
+
headers = hparser.formatHeaders(headers, rawHeaders);
|
|
2109
|
+
headers = hparser.getRawHeaders(headers);
|
|
2110
|
+
}
|
|
2111
|
+
return (headers ? statusLine + '\r\n' + headers : statusLine) + '\r\n\r\n';
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
exports.getHttpMeta = getHttpMeta;
|
package/lib/util/data-server.js
CHANGED
|
@@ -31,6 +31,58 @@ var framesCache = [];
|
|
|
31
31
|
var framesMap = {};
|
|
32
32
|
var proxy, binded;
|
|
33
33
|
var clearCount = 0;
|
|
34
|
+
var MAX_COUNT = 120;
|
|
35
|
+
|
|
36
|
+
function formatApiOptions(options) {
|
|
37
|
+
var result = { count: MAX_COUNT };
|
|
38
|
+
if (options) {
|
|
39
|
+
var count = options.count;
|
|
40
|
+
if (count > 0 && count < MAX_COUNT) {
|
|
41
|
+
result.count = count;
|
|
42
|
+
}
|
|
43
|
+
var startTime = options.startTime > 0 ? options.startTime + '' : null;
|
|
44
|
+
var startId = util.isString(options.startId) ? options.startId : null;
|
|
45
|
+
if (startId && startTime && startId < startTime) {
|
|
46
|
+
startId = startTime;
|
|
47
|
+
}
|
|
48
|
+
result.startId = startId || startTime;
|
|
49
|
+
result.latest = options.latest;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getItemId(item, name) {
|
|
55
|
+
return name ? item[name] : item;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getIdIndex(arr, target, name) {
|
|
59
|
+
if (!target) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
var left = 0;
|
|
63
|
+
var right = arr.length - 1;
|
|
64
|
+
|
|
65
|
+
while (left <= right) {
|
|
66
|
+
if (getItemId(arr[left], name) > target) {
|
|
67
|
+
return left;
|
|
68
|
+
}
|
|
69
|
+
var count = right - left;
|
|
70
|
+
if (!count) {
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
if (count === 1) {
|
|
74
|
+
return getItemId(arr[right], name) > target ? right : -1;
|
|
75
|
+
}
|
|
76
|
+
var mid = left + Math.floor(count / 2);
|
|
77
|
+
var val = getItemId(arr[mid], name);
|
|
78
|
+
if (val > target) {
|
|
79
|
+
right = mid;
|
|
80
|
+
} else {
|
|
81
|
+
left = mid + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
34
86
|
|
|
35
87
|
function enable() {
|
|
36
88
|
if (binded) {
|
|
@@ -595,6 +647,227 @@ module.exports = function init(_proxy) {
|
|
|
595
647
|
return getFrames(options.curReqId, options.lastFrameId);
|
|
596
648
|
};
|
|
597
649
|
|
|
650
|
+
proxy.getFramesForApi = function (options) {
|
|
651
|
+
var reqId = options.reqId;
|
|
652
|
+
var isServer = options.from === 'server';
|
|
653
|
+
var isClient = options.from === 'client';
|
|
654
|
+
var isAll = !isServer && !isClient;
|
|
655
|
+
var params = formatApiOptions(options);
|
|
656
|
+
var index = getIdIndex(framesCache, params.startId, 'frameId');
|
|
657
|
+
var result = [];
|
|
658
|
+
if (index === -1) {
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
var count = params.count;
|
|
662
|
+
var len = framesCache.length;
|
|
663
|
+
var addFrame = function(i) {
|
|
664
|
+
var frame = framesCache[i];
|
|
665
|
+
if ((isAll || isClient === !!frame.isClient) && frame.reqId === reqId) {
|
|
666
|
+
result.push(decodeData(frame));
|
|
667
|
+
if (--count <= 0) {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
if (params.latest) {
|
|
673
|
+
for (var i = len - 1; i >= index; i--) {
|
|
674
|
+
if (addFrame(i)) {
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
result.reverse();
|
|
679
|
+
} else {
|
|
680
|
+
for (; index < len; index++) {
|
|
681
|
+
if (addFrame(index)) {
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return result;
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
function checkUrl(session, subUrl) {
|
|
690
|
+
if (!subUrl) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
var url = session.isHttps ? 'tunnel://' + session.url : session.url.toLowerCase();
|
|
694
|
+
return url.indexOf(subUrl) !== -1;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
var WS_RE = /^wss?:\/\//;
|
|
698
|
+
var FONT_RE = /font\//i;
|
|
699
|
+
var MEDIA_RE = /audio\/|video\/|application\/vnd.apple.mpegurl|application\/dash+xml/i;
|
|
700
|
+
var WASM_RE = /application\/wasm/i;
|
|
701
|
+
var TYPES = ['IMG', 'HTML', 'CSS', 'JS', 'JSON'];
|
|
702
|
+
var ALIAS_TYPES = {
|
|
703
|
+
WEBSOCKET: 'WS',
|
|
704
|
+
WSS: 'WS',
|
|
705
|
+
IMAGE: 'IMG',
|
|
706
|
+
JAVASCRIPT: 'JS',
|
|
707
|
+
STYLE: 'CSS',
|
|
708
|
+
MATCHED: 'RULES',
|
|
709
|
+
EDIT: 'COMPOSER'
|
|
710
|
+
};
|
|
711
|
+
var RAW_TYPES = ['FONT', 'MEDIA', 'WASM'];
|
|
712
|
+
|
|
713
|
+
function hasError(item) {
|
|
714
|
+
var statusCode = item.res && item.res.statusCode;
|
|
715
|
+
return item.reqError || item.resError || (item.customData && item.customData.error) ||
|
|
716
|
+
(statusCode && (!/^\d+$/.test(statusCode) || statusCode >= 400));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function checkType(session, filterType) {
|
|
720
|
+
if (!filterType) {
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
filterType = ALIAS_TYPES[filterType] || filterType;
|
|
724
|
+
if (filterType === 'CAPTUREERROR') {
|
|
725
|
+
return session.captureError;
|
|
726
|
+
}
|
|
727
|
+
if (filterType === 'WS') {
|
|
728
|
+
return WS_RE.test(session.url);
|
|
729
|
+
}
|
|
730
|
+
if (filterType === 'TUNNEL') {
|
|
731
|
+
return session.isHttps;
|
|
732
|
+
}
|
|
733
|
+
if (filterType === 'RULES') {
|
|
734
|
+
return session.rules && Object.keys(session.rules).length > 0;
|
|
735
|
+
}
|
|
736
|
+
var rawType = util.getRawType(session.res);
|
|
737
|
+
var type;
|
|
738
|
+
for (var i = 0, len = RAW_TYPES.length; i < len; i++) {
|
|
739
|
+
type = RAW_TYPES[i];
|
|
740
|
+
if (filterType === type) {
|
|
741
|
+
var p = type === 'FONT' ? FONT_RE : type === 'MEDIA' ? MEDIA_RE : WASM_RE;
|
|
742
|
+
return p.test(rawType);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
var curType = util.getContentType(rawType);
|
|
747
|
+
for (i = 0, len = TYPES.length; i < len; i++) {
|
|
748
|
+
type = TYPES[i];
|
|
749
|
+
if (filterType === type) {
|
|
750
|
+
return curType === (i ? type : 'IMG');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (filterType === 'ERROR') {
|
|
754
|
+
return hasError(session);
|
|
755
|
+
}
|
|
756
|
+
if (filterType === 'MOCK') {
|
|
757
|
+
var rules = session.rules || '';
|
|
758
|
+
return rules.rule && rules.rule.isLoc;
|
|
759
|
+
}
|
|
760
|
+
if (filterType === 'IMPORT') {
|
|
761
|
+
return session.importedData;
|
|
762
|
+
}
|
|
763
|
+
if (filterType === 'COMPOSER') {
|
|
764
|
+
return session.fc;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function formatHeaderFilter(filter) {
|
|
769
|
+
if (!filter) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
var name = filter.name || filter.key;
|
|
773
|
+
if (typeof name !== 'string') {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
name: name.trim(),
|
|
778
|
+
subValue: filter.subValue == null ? null : String(filter.subValue).toLowerCase()
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function filterHeader(headers, filter) {
|
|
783
|
+
if (!filter) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
if (!headers) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
var value = headers[filter.name];
|
|
790
|
+
if (value == null) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
var subValue = filter.subValue;
|
|
794
|
+
var noSubValue = !subValue;
|
|
795
|
+
if (noSubValue || !value) {
|
|
796
|
+
return noSubValue;
|
|
797
|
+
}
|
|
798
|
+
value = Array.isArray(value) ? value.join('\n') : String(value);
|
|
799
|
+
return value.toLowerCase().indexOf(subValue) !== -1;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function checkMethod(session, method) {
|
|
803
|
+
if (!method) {
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
return method.indexOf(session.req.method) !== -1;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function checkStatus(session, status) {
|
|
810
|
+
return !status || status.indexOf(String(session.res.statusCode).toUpperCase()) !== -1;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
proxy.getSessionsForApi = function (options) {
|
|
815
|
+
var params = formatApiOptions(options);
|
|
816
|
+
var index = getIdIndex(ids, params.startId);
|
|
817
|
+
var result = [];
|
|
818
|
+
if (index === -1) {
|
|
819
|
+
return result;
|
|
820
|
+
}
|
|
821
|
+
var count = params.count;
|
|
822
|
+
var len = ids.length;
|
|
823
|
+
var subUrl = util.getString(options.subUrl).toLowerCase();
|
|
824
|
+
var type = util.getString(options.type).toUpperCase();
|
|
825
|
+
var method = options.method;
|
|
826
|
+
var status = options.statusCode || options.status;
|
|
827
|
+
var reqHeader = formatHeaderFilter(options.reqHeader);
|
|
828
|
+
var resHeader = formatHeaderFilter(options.resHeader);
|
|
829
|
+
if (Array.isArray(method)) {
|
|
830
|
+
method = method.join();
|
|
831
|
+
} else {
|
|
832
|
+
method = util.getString(method);
|
|
833
|
+
}
|
|
834
|
+
if (Array.isArray(status)) {
|
|
835
|
+
status = status.join();
|
|
836
|
+
} else if (status) {
|
|
837
|
+
status = String(status);
|
|
838
|
+
}
|
|
839
|
+
method = method && method.toUpperCase().split(',');
|
|
840
|
+
status = status && status.toUpperCase().split(',');
|
|
841
|
+
var addSession = function(i) {
|
|
842
|
+
var session = reqData[ids[i]];
|
|
843
|
+
if (checkUrl(session, subUrl) && checkMethod(session, method)
|
|
844
|
+
&& checkStatus(session, status) && filterHeader(session.req.headers, reqHeader)
|
|
845
|
+
&& filterHeader(session.res.headers, resHeader) && checkType(session, type)) {
|
|
846
|
+
toBase64String(session.req);
|
|
847
|
+
toBase64String(session.res);
|
|
848
|
+
result.push(session);
|
|
849
|
+
if (--count <= 0) {
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
if (params.latest) {
|
|
855
|
+
for (var i = len - 1; i >= index; i--) {
|
|
856
|
+
if (addSession(i)) {
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
result.reverse();
|
|
861
|
+
} else {
|
|
862
|
+
for (; index < len; index++) {
|
|
863
|
+
if (addSession(index)) {
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return result;
|
|
869
|
+
};
|
|
870
|
+
|
|
598
871
|
function getNetworkTime(id) {
|
|
599
872
|
var item = id && reqData[id];
|
|
600
873
|
return item && {
|
package/lib/util/index.js
CHANGED
|
@@ -126,7 +126,10 @@ exports.ADDITIONAL_HEAD = 'x-whistle-additional-headers';
|
|
|
126
126
|
workerIndex = workerIndex >= 0 ? common.padLeft(config.workerIndex, 3) : '';
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
exports.toString = common.toString;
|
|
129
130
|
exports.sendRes = common.sendRes;
|
|
131
|
+
exports.setDelayTimer = common.setDelayTimer;
|
|
132
|
+
exports.clearDelayTimer = common.clearDelayTimer;
|
|
130
133
|
exports.trimStr = trimStr;
|
|
131
134
|
exports.THEME_STYLE = THEME_STYLE;
|
|
132
135
|
exports.encodeNonLatin1Char = encodeNonLatin1Char;
|
|
@@ -2464,10 +2467,7 @@ exports.getPluginModal = function(conf) {
|
|
|
2464
2467
|
};
|
|
2465
2468
|
|
|
2466
2469
|
function getString(str) {
|
|
2467
|
-
|
|
2468
|
-
return;
|
|
2469
|
-
}
|
|
2470
|
-
return str.trim();
|
|
2470
|
+
return isString(str) ? str.trim() : '';
|
|
2471
2471
|
}
|
|
2472
2472
|
|
|
2473
2473
|
exports.getString = getString;
|
|
@@ -2637,19 +2637,6 @@ exports.getStaticDir = function (conf) {
|
|
|
2637
2637
|
return toRelPath(staticDir.replace(/^\/+/, ''));
|
|
2638
2638
|
};
|
|
2639
2639
|
|
|
2640
|
-
function toString(str) {
|
|
2641
|
-
if (str != null) {
|
|
2642
|
-
if (typeof str === 'string') {
|
|
2643
|
-
return str;
|
|
2644
|
-
}
|
|
2645
|
-
try {
|
|
2646
|
-
return JSON.stringify(str) || '';
|
|
2647
|
-
} catch (e) {}
|
|
2648
|
-
}
|
|
2649
|
-
return '';
|
|
2650
|
-
}
|
|
2651
|
-
exports.toString = toString;
|
|
2652
|
-
|
|
2653
2640
|
var index = 0;
|
|
2654
2641
|
|
|
2655
2642
|
exports.getReqId = function (now) {
|
|
@@ -2977,7 +2964,7 @@ exports.setResCors = function (data, cors, req) {
|
|
|
2977
2964
|
} else if (autoComp) {
|
|
2978
2965
|
var method = req.headers['access-control-request-method'];
|
|
2979
2966
|
if (method) {
|
|
2980
|
-
setHeader(data, 'access-control-allow-
|
|
2967
|
+
setHeader(data, 'access-control-allow-methods', method);
|
|
2981
2968
|
}
|
|
2982
2969
|
}
|
|
2983
2970
|
|
|
@@ -3624,7 +3611,7 @@ function readOneChunk(stream, callback, timeout) {
|
|
|
3624
3611
|
}
|
|
3625
3612
|
var timer;
|
|
3626
3613
|
var handler = function (chunk) {
|
|
3627
|
-
|
|
3614
|
+
clearTimeout(timer);
|
|
3628
3615
|
stream.pause();
|
|
3629
3616
|
stream.removeListener('data', handler);
|
|
3630
3617
|
stream.removeListener('end', handler);
|
|
@@ -3698,10 +3685,9 @@ exports.getAuthBasic = function (auth) {
|
|
|
3698
3685
|
|
|
3699
3686
|
exports.delay = function (time, callback) {
|
|
3700
3687
|
if (time > 0) {
|
|
3701
|
-
setTimeout(callback, time);
|
|
3702
|
-
} else {
|
|
3703
|
-
callback();
|
|
3688
|
+
return setTimeout(callback, time);
|
|
3704
3689
|
}
|
|
3690
|
+
callback();
|
|
3705
3691
|
};
|
|
3706
3692
|
|
|
3707
3693
|
var F_HOST_RE = /\bhost\b/i;
|
package/lib/util/patch.js
CHANGED
|
@@ -4,6 +4,7 @@ var Socket = require('net').Socket;
|
|
|
4
4
|
var http = require('http');
|
|
5
5
|
var https = require('https');
|
|
6
6
|
var hparser = require('hparser');
|
|
7
|
+
var common = require('./common');
|
|
7
8
|
|
|
8
9
|
var httpRequest = http.request;
|
|
9
10
|
var httpsRequest = https.request;
|
|
@@ -133,12 +134,7 @@ http.request = function (options) {
|
|
|
133
134
|
headers['x-whistle-policy'] = 'intercept';
|
|
134
135
|
delete headers['X-Whistle-Policy'];
|
|
135
136
|
}
|
|
136
|
-
|
|
137
|
-
var rawData = 'CONNECT ' + tunnelPath + ' HTTP/1.1';
|
|
138
|
-
if (headers) {
|
|
139
|
-
rawData += '\r\n' + headers;
|
|
140
|
-
}
|
|
141
|
-
rawData += '\r\n\r\n';
|
|
137
|
+
var rawData = common.getHttpMeta('CONNECT', tunnelPath, 'HTTP/1.1', headers);
|
|
142
138
|
if (res.statusCode === 200 && res.headers['x-whistle-allow-tunnel-ack']) {
|
|
143
139
|
rawData = '1' + rawData;
|
|
144
140
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
var Transform = require('pipestream').Transform;
|
|
2
|
-
var
|
|
2
|
+
var inherits = require('util').inherits;
|
|
3
|
+
var setDelayTimer = require('./common').setDelayTimer;
|
|
3
4
|
|
|
4
|
-
function SpeedTransform(options) {
|
|
5
|
+
function SpeedTransform(options, req) {
|
|
5
6
|
Transform.call(this);
|
|
6
7
|
options = options || {};
|
|
8
|
+
this._req = req;
|
|
7
9
|
var value = parseInt((options.speed * 1000) / 8);
|
|
8
10
|
if (value > 0) {
|
|
9
11
|
this._speed = value;
|
|
@@ -13,13 +15,22 @@ function SpeedTransform(options) {
|
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
inherits(SpeedTransform, Transform);
|
|
17
19
|
|
|
18
|
-
SpeedTransform.prototype
|
|
20
|
+
var proto = SpeedTransform.prototype;
|
|
21
|
+
|
|
22
|
+
proto._setTimeout = function (callback, delay) {
|
|
23
|
+
var timer = setTimeout(callback, delay);
|
|
24
|
+
if (this._req) {
|
|
25
|
+
setDelayTimer(this._req, timer);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
proto._transform = function (chunk, encoding, callback) {
|
|
19
30
|
var self = this;
|
|
20
31
|
var cb = function () {
|
|
21
32
|
if (chunk && self._speed) {
|
|
22
|
-
|
|
33
|
+
self._setTimeout(function () {
|
|
23
34
|
callback(null, chunk);
|
|
24
35
|
}, Math.round((chunk.length * 1000) / self._speed));
|
|
25
36
|
} else {
|
|
@@ -30,7 +41,7 @@ SpeedTransform.prototype._transform = function (chunk, encoding, callback) {
|
|
|
30
41
|
if (self._delay) {
|
|
31
42
|
var delay = self._delay;
|
|
32
43
|
self._delay = null;
|
|
33
|
-
return
|
|
44
|
+
return self._setTimeout(cb, delay);
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
cb();
|