whistle 2.9.74 → 2.9.76-beta

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.
@@ -6,7 +6,6 @@ var https = require('https');
6
6
  var parseReqUrl = require('parseurl');
7
7
  var bodyParser = require('body-parser');
8
8
  var crypto = require('crypto');
9
- var cookie = require('cookie');
10
9
  var fs = require('fs');
11
10
  var zlib = require('zlib');
12
11
  var extend = require('extend');
@@ -69,6 +68,26 @@ function shasum(str) {
69
68
  return shasum.digest('hex');
70
69
  }
71
70
 
71
+ function parseCookie(str) {
72
+ var result = {};
73
+ str && str.split(';').forEach(function(pair) {
74
+ var index = pair.indexOf('=');
75
+ if (index === -1) {
76
+ return;
77
+ }
78
+ var key = pair.substring(0, index).trim();
79
+ var val = pair.substring(index + 1).trim();
80
+ if (result[key] == null) {
81
+ try {
82
+ result[key] = decodeURIComponent(val);
83
+ } catch (e) {
84
+ result[key] = val;
85
+ }
86
+ }
87
+ });
88
+ return result;
89
+ }
90
+
72
91
  function getLoginKey (req, res, auth) {
73
92
  var ip = util.getClientIp(req);
74
93
  var password = auth.password;
@@ -112,7 +131,7 @@ function verifyLogin(req, res, auth) {
112
131
  return true;
113
132
  }
114
133
  var authKey = auth.authKey;
115
- var cookies = cookie.parse(req.headers.cookie || '');
134
+ var cookies = parseCookie(req.headers.cookie);
116
135
  var lkey = cookies[authKey];
117
136
  var correctKey = getLoginKey(req, res, auth);
118
137
  if (correctKey === lkey) {
@@ -125,12 +144,12 @@ function verifyLogin(req, res, auth) {
125
144
  queryAuth.pass = queryAuth.pass && shasum(queryAuth.pass);
126
145
  }
127
146
  if (equalAuth(headerAuth, auth) || equalAuth(queryAuth, auth)) {
128
- var options = {
129
- expires: new Date(Date.now() + (MAX_AGE * 1000)),
130
- maxAge: MAX_AGE,
131
- path: '/'
132
- };
133
- res.setHeader('Set-Cookie', cookie.serialize(authKey, correctKey, options));
147
+ res.setHeader('Set-Cookie', [
148
+ authKey + '=' + util.encodeURIComponent(correctKey),
149
+ 'Max-Age=' + MAX_AGE,
150
+ 'Path=/',
151
+ 'Expires=' + new Date(Date.now() + (MAX_AGE * 1000)).toUTCString()
152
+ ].join('; '));
134
153
  return true;
135
154
  }
136
155
  }
@@ -17,6 +17,15 @@ var VAR_RE = /\{\S+\}/;
17
17
  var QUERY_VAR_RE = /\$?(?:\{\{([\w$-]+)\}\}|\{([\w$-]+)\})/g;
18
18
  var UTF8_OPTIONS = { encoding: 'utf8' };
19
19
  var ENABLE_OPTIONS = { enable: true };
20
+ var SLASH_RE = /\\+/g;
21
+ var QUOTE_RE = /"/g;
22
+ var JS_RE = /^js:/i;
23
+ var HTML_RE = /^html:/i;
24
+ var BLANK_RE = /\s/g;
25
+
26
+ function urlToStr(url) {
27
+ return url.replace(SLASH_RE, '').replace(QUOTE_RE, '\\$&').replace(BLANK_RE, ' ');
28
+ }
20
29
 
21
30
  function isRawFileProtocol(protocol) {
22
31
  return RAW_FILE_RE.test(protocol);
@@ -176,6 +185,34 @@ function sendResponse(rule, res, reader, req) {
176
185
  res.response(reader);
177
186
  }
178
187
 
188
+ function handleLocHref(req, rule, handleRes) {
189
+ var body = (util.getMatcherValue(rule) || '').trim();
190
+ var isJs = JS_RE.test(body);
191
+ var isHtml = HTML_RE.test(body);
192
+ if (isJs) {
193
+ body = body.substring(3);
194
+ } else if (isHtml) {
195
+ body = body.substring(5);
196
+ } else {
197
+ isJs = req.headers['sec-fetch-dest'] === 'script';
198
+ }
199
+ var type = isJs ? 'application/javascript' : 'text/html';
200
+ if (body) {
201
+ body = 'window.location.href = "' + urlToStr(body) + '";';
202
+ if (!isJs) {
203
+ body = '<script>' + body + '</script>';
204
+ }
205
+ }
206
+
207
+ handleRes({
208
+ statusCode: 200,
209
+ body: body,
210
+ headers: {
211
+ 'content-type': type + '; charset=utf-8'
212
+ }
213
+ });
214
+ }
215
+
179
216
  module.exports = function (req, res, next) {
180
217
  var options = req.options;
181
218
  var config = this.config;
@@ -185,6 +222,9 @@ module.exports = function (req, res, next) {
185
222
  }
186
223
  var rules = req.rules;
187
224
  var rule = rules.rule;
225
+ if (protoMgr.isLocHref(protocol)) {
226
+ return handleLocHref(req, rule, render);
227
+ }
188
228
  if (isAutoCors(req, rule) && req.method === 'OPTIONS') {
189
229
  var optsRes = util.wrapResponse({ statusCode: 200 });
190
230
  optsRes.realUrl = rule.matcher;
@@ -134,9 +134,9 @@ function handleReplace(res, replacement) {
134
134
  util.isOriginalRegExp(pattern) &&
135
135
  (pattern = util.toOriginalRegExp(pattern))
136
136
  ) {
137
- res.addTextTransform(new ReplacePatternTransform(pattern, value));
137
+ res.addTextTransform(new ReplacePatternTransform(pattern, value, util.isSSE(res)));
138
138
  } else if (pattern) {
139
- res.addTextTransform(new ReplaceStringTransform(pattern, value));
139
+ res.addTextTransform(new ReplaceStringTransform(pattern, value, util.isSSE(res)));
140
140
  }
141
141
  });
142
142
  }
@@ -74,6 +74,7 @@ var protocols = [
74
74
  ];
75
75
 
76
76
  var RULE_RE = /^(?:|x|xs)(?:file|rawfile|dust|tpl|jsonp):/;
77
+ var LOC_RE = /^locationHref:/;
77
78
  var PROXY_RE =
78
79
  /^x?(?:socks|proxy|https?-proxy|internal-proxy|internal-https?-proxy|https2http-proxy|http2https-proxy)$/;
79
80
  var pureResProtocols = [
@@ -275,11 +276,15 @@ function isWebsocketProtocol(protocol) {
275
276
  exports.isWebsocketProtocol = isWebsocketProtocol;
276
277
 
277
278
  function isFileProxy(protocol) {
278
- return RULE_RE.test(protocol);
279
+ return RULE_RE.test(protocol) || LOC_RE.test(protocol);
279
280
  }
280
281
 
281
282
  exports.isFileProxy = isFileProxy;
282
283
 
284
+ exports.isLocHref = function(protocol) {
285
+ return LOC_RE.test(protocol);
286
+ };
287
+
283
288
  function contains(name) {
284
289
  if (
285
290
  protocols.indexOf(name) != -1 ||
package/lib/util/index.js CHANGED
@@ -15,7 +15,6 @@ var iconv = require('iconv-lite');
15
15
  var zlib = require('zlib');
16
16
  var dns = require('dns');
17
17
  var PipeStream = require('pipestream');
18
- var Q = require('q');
19
18
  var Buffer = require('safe-buffer').Buffer;
20
19
  var protoMgr = require('../rules/protocols');
21
20
  var protocols = protoMgr.protocols;
@@ -438,6 +437,12 @@ function stat(file, callback, force) {
438
437
  });
439
438
  }
440
439
 
440
+ function spreadPromise(promise, callback) {
441
+ promise.then(function(list) {
442
+ callback.apply(null, list);
443
+ });
444
+ }
445
+
441
446
  function getFileWriter(file, callback, force) {
442
447
  if (!file) {
443
448
  return callback();
@@ -476,19 +481,19 @@ function getFileWriters(files, callback, force) {
476
481
  files = [files];
477
482
  }
478
483
 
479
- Q.all(
484
+ spreadPromise(Promise.all(
480
485
  files.map(function (file) {
481
- var defer = Q.defer();
482
- getFileWriter(
483
- file,
484
- function (writer) {
485
- defer.resolve(writer);
486
- },
487
- force
488
- );
489
- return defer.promise;
486
+ return new Promise(function(resolve) {
487
+ getFileWriter(
488
+ file,
489
+ function (writer) {
490
+ resolve(writer);
491
+ },
492
+ force
493
+ );
494
+ });
490
495
  })
491
- ).spread(callback);
496
+ ), callback);
492
497
  }
493
498
 
494
499
  exports.getFileWriters = getFileWriters;
@@ -1248,22 +1253,13 @@ exports.parseRuleJson = function(rules, callback, req) {
1248
1253
  if (!Array.isArray(rules)) {
1249
1254
  rules = [rules];
1250
1255
  }
1251
- Q.all(
1256
+ spreadPromise(Promise.all(
1252
1257
  rules.map(function (rule) {
1253
- var defer = Q.defer();
1254
- readRuleList(
1255
- rule,
1256
- function (data) {
1257
- defer.resolve(data);
1258
- },
1259
- true,
1260
- null,
1261
- null,
1262
- req
1263
- );
1264
- return defer.promise;
1258
+ return new Promise(function(resolve) {
1259
+ readRuleList(rule, resolve, true, null, null, req);
1260
+ });
1265
1261
  })
1266
- ).spread(callback);
1262
+ ), callback);
1267
1263
  };
1268
1264
 
1269
1265
  function getTempFilePath(filePath, rule) {
@@ -1487,23 +1483,13 @@ exports.getRuleValue = function(rules, callback, noBody, charset, isHtml, req) {
1487
1483
  if (!Array.isArray(rules)) {
1488
1484
  rules = [rules];
1489
1485
  }
1490
-
1491
- Q.all(
1486
+ spreadPromise(Promise.all(
1492
1487
  rules.map(function (rule) {
1493
- var defer = Q.defer();
1494
- readRuleList(
1495
- rule,
1496
- function (data) {
1497
- defer.resolve(data);
1498
- },
1499
- false,
1500
- charset,
1501
- isHtml,
1502
- req
1503
- );
1504
- return defer.promise;
1488
+ return new Promise(function(resolve) {
1489
+ readRuleList(rule, resolve, false, charset, isHtml, req);
1490
+ });
1505
1491
  })
1506
- ).spread(callback);
1492
+ ), callback);
1507
1493
  };
1508
1494
 
1509
1495
  function decodePath(path) {
@@ -3681,18 +3667,17 @@ exports.getBoundIp = function (host, cb) {
3681
3667
  }
3682
3668
  var boundIpDefer = boundIpDeferMap[host];
3683
3669
  if (boundIpDefer) {
3684
- return boundIpDefer.done(cb);
3685
- }
3686
- var defer = Q.defer();
3687
- boundIpDefer = defer.promise;
3688
- boundIpDeferMap[host] = boundIpDefer;
3689
- boundIpDefer.done(cb);
3690
- dns.lookup(host, function (err, ip) {
3691
- if (err) {
3692
- throw err;
3693
- }
3694
- defer.resolve(ip);
3670
+ return boundIpDefer.then(cb);
3671
+ }
3672
+ boundIpDeferMap[host] = boundIpDefer = new Promise(function(resolve) {
3673
+ dns.lookup(host, function (err, ip) {
3674
+ if (err) {
3675
+ throw err;
3676
+ }
3677
+ resolve(ip);
3678
+ });
3695
3679
  });
3680
+ boundIpDefer.then(cb);
3696
3681
  };
3697
3682
 
3698
3683
  function getPluginConfig(conf, name) {
@@ -4122,3 +4107,9 @@ exports.needAbortRes = function(req) {
4122
4107
  }
4123
4108
  return req.isTunnel && disable.tunnel;
4124
4109
  };
4110
+
4111
+ var SSE_RE = /^\s*text\/event-stream\s*;?/i;
4112
+
4113
+ exports.isSSE = function(res) {
4114
+ return SSE_RE.test(res.headers['content-type']);
4115
+ };
@@ -7,11 +7,12 @@ var SUB_MATCH_RE = /(^|\\{0,2})?(\$\$?(b)?[&\d])/g;
7
7
  var ALL_RE = /^\/\.[*+]\/g?i?g?$/;
8
8
  var MAX_SUB_MATCH_LEN = 512;
9
9
 
10
- function ReplacePatternTransform(pattern, value) {
10
+ function ReplacePatternTransform(pattern, value, isSSE) {
11
11
  Transform.call(this);
12
12
  this._pattern = pattern;
13
13
  this._replaceAll = ALL_RE.test(pattern);
14
14
  this._value = value == null ? '' : value + '';
15
+ this._isSSE = isSSE;
15
16
  this._rest = '';
16
17
  }
17
18
 
@@ -39,6 +40,12 @@ proto._transform = function (chunk, _, callback) {
39
40
  return replacePattern(value, arguments);
40
41
  });
41
42
  index = Math.max(index, chunk.length - LENGTH);
43
+ if (this._isSSE) {
44
+ var endIndex = chunk.lastIndexOf('\n\n');
45
+ if (endIndex !== -1) {
46
+ index = Math.max(endIndex + 2, index);
47
+ }
48
+ }
42
49
  this._rest = chunk.substring(index);
43
50
  chunk = result.substring(0, result.length - this._rest.length);
44
51
  } else if (this._rest) {
@@ -1,18 +1,19 @@
1
1
  var Transform = require('pipestream').Transform;
2
2
  var util = require('util');
3
3
 
4
- function ReplaceStringTransform(str, value) {
4
+ function ReplaceStringTransform(str, value, isSSE) {
5
5
  Transform.call(this);
6
6
  this._str = str;
7
7
  this._length = this._str.length;
8
8
  this._value = value == null ? '' : value + '';
9
+ this._isSSE = isSSE;
9
10
  this._rest = '';
10
11
  }
11
12
 
12
13
  util.inherits(ReplaceStringTransform, Transform);
13
14
 
14
15
  var proto = ReplaceStringTransform.prototype;
15
- proto._transform = function (chunk, encoding, callback) {
16
+ proto._transform = function (chunk, _, callback) {
16
17
  if (chunk != null) {
17
18
  chunk = this._rest + chunk;
18
19
  var minIndex = chunk.length + 1 - this._length;
@@ -23,6 +24,12 @@ proto._transform = function (chunk, encoding, callback) {
23
24
  } else {
24
25
  index = minIndex;
25
26
  }
27
+ if (this._isSSE) {
28
+ var endIndex = chunk.lastIndexOf('\n\n');
29
+ if (endIndex !== -1) {
30
+ index = Math.max(endIndex + 2, index);
31
+ }
32
+ }
26
33
  this._rest = chunk.substring(index);
27
34
  chunk = chunk.substring(0, index);
28
35
  } else {
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.9.74",
4
+ "version": "2.9.76-beta",
5
5
  "dataDirname": ".whistle",
6
6
  "localUIHost": "local.whistlejs.com",
7
7
  "port": 8899,
@@ -37,11 +37,10 @@
37
37
  "async-limiter": "2.0.0",
38
38
  "body-parser": "^1.19.0",
39
39
  "colors": "1.1.2",
40
- "cookie": "^0.3.1",
41
40
  "express": "^4.19.2",
42
41
  "extend": "^3.0.2",
43
42
  "fs-extra2": "^1.0.0",
44
- "hagent": "^0.9.0",
43
+ "hagent": "^0.9.2",
45
44
  "hparser": "^0.4.0",
46
45
  "iconv-lite": "^0.4.24",
47
46
  "json5": "^2.2.3",
@@ -51,11 +50,10 @@
51
50
  "node-forge": "^1.3.1",
52
51
  "node-pac": "^0.5.0",
53
52
  "parseurl": "^1.3.1",
54
- "pfork": "^0.6.1",
53
+ "pfork": "^0.6.2",
55
54
  "pipestream": "^0.7.3",
56
- "q": "1.4.1",
57
55
  "safe-buffer": "^5.1.2",
58
- "set-global-proxy": "^0.1.11",
56
+ "set-global-proxy": "0.2.0-beta",
59
57
  "sni": "1.0.0",
60
58
  "sockx": "^0.2.1",
61
59
  "starting": "^8.0.2",