whistle 2.10.1 → 2.10.3

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 (46) hide show
  1. package/assets/js/log.js +44 -27
  2. package/assets/menu.html +2 -0
  3. package/assets/modal.html +2 -0
  4. package/assets/tab.html +3 -3
  5. package/bin/use.js +236 -101
  6. package/bin/util.js +10 -7
  7. package/bin/whistle.js +5 -5
  8. package/biz/webui/cgi-bin/download.js +1 -1
  9. package/biz/webui/cgi-bin/get-data.js +0 -5
  10. package/biz/webui/cgi-bin/init.js +0 -2
  11. package/biz/webui/cgi-bin/rules/clear-dns-cache.js +7 -0
  12. package/biz/webui/htdocs/index.html +2 -2
  13. package/biz/webui/htdocs/js/index.js +53 -50
  14. package/biz/webui/lib/index.js +49 -4
  15. package/lib/config.js +6 -4
  16. package/lib/handlers/file-proxy.js +1 -1
  17. package/lib/https/ca.js +39 -9
  18. package/lib/https/index.js +109 -114
  19. package/lib/index.js +1 -5
  20. package/lib/inspectors/data.js +119 -13
  21. package/lib/inspectors/log.js +4 -0
  22. package/lib/inspectors/req.js +5 -3
  23. package/lib/inspectors/weinre.js +7 -2
  24. package/lib/plugins/get-plugins-sync.js +15 -11
  25. package/lib/plugins/get-plugins.js +11 -12
  26. package/lib/plugins/load-plugin.js +1 -1
  27. package/lib/plugins/module-paths.js +4 -0
  28. package/lib/plugins/util.js +8 -0
  29. package/lib/rules/dns.js +4 -0
  30. package/lib/rules/index.js +0 -1
  31. package/lib/rules/protocols.js +3 -9
  32. package/lib/rules/rules.js +21 -22
  33. package/lib/service/composer.js +10 -5
  34. package/lib/{util/log-server.js → service/console-log.js} +37 -34
  35. package/lib/service/index.js +2 -4
  36. package/lib/service/service.js +64 -15
  37. package/lib/service/util.js +3 -42
  38. package/lib/socket-mgr.js +2 -16
  39. package/lib/tunnel.js +2 -4
  40. package/lib/upgrade.js +1 -1
  41. package/lib/util/common.js +115 -17
  42. package/lib/util/index.js +48 -40
  43. package/package.json +2 -2
  44. package/biz/webui/cgi-bin/import-remote.js +0 -37
  45. package/biz/webui/cgi-bin/log/set.js +0 -7
  46. package/lib/util/parse-query.js +0 -35
@@ -38,7 +38,12 @@ function saveComposerHistory() {
38
38
  } catch (e) {}
39
39
  }
40
40
 
41
- function handleComposerHistory(data) {
41
+ function getUrl(url, isConn) {
42
+ url = isConn ? url.replace(/[?#].*$/, '') : url;
43
+ return url.length > MAX_URL_LEN ? url.substring(0, MAX_URL_LEN) : url;
44
+ }
45
+
46
+ function handleComposerHistory(data, isConn) {
42
47
  var url = data.url;
43
48
  var method = data.method;
44
49
  var headers = data.headers;
@@ -50,7 +55,7 @@ function handleComposerHistory(data) {
50
55
  var result = {
51
56
  date: Date.now(),
52
57
  useH2: data.useH2,
53
- url: url.length > MAX_URL_LEN ? url.substring(0, MAX_URL_LEN) : url,
58
+ url: getUrl(url, isConn),
54
59
  method:
55
60
  method.length > MAX_METHOD_LEN
56
61
  ? method.substring(0, MAX_METHOD_LEN)
@@ -335,13 +340,13 @@ function getCharset(headers) {
335
340
  exports.handleRequest = function(req, res) {
336
341
  var fullUrl = req.body.url;
337
342
  if (!fullUrl || typeof fullUrl !== 'string') {
338
- return res.json({ec: 0});
343
+ return res.json({ec: 2, em: 'Invalid URL'});
339
344
  }
340
345
 
341
346
  fullUrl = common.encodeNonLatin1Char(fullUrl.replace(/#.*$/, ''));
342
347
  var options = common.parseUrl(common.setProtocol(fullUrl));
343
348
  if (!options.host) {
344
- return res.json({ec: 0});
349
+ return res.json({ec: 2, em: 'Invalid URL'});
345
350
  }
346
351
  var protocol = options.protocol;
347
352
  if (protocol) {
@@ -386,7 +391,7 @@ exports.handleRequest = function(req, res) {
386
391
  }
387
392
  }
388
393
  if (!req.body.noStore && req.body.needResponse && common.checkHistory(req.body)) {
389
- handleComposerHistory(req.body);
394
+ handleComposerHistory(req.body, isConn);
390
395
  dataCenter.saveData({
391
396
  type: 'composer',
392
397
  history: composerHistory
@@ -1,6 +1,6 @@
1
1
  var MAX_LENGTH = 800;
2
2
  var MIN_LENGTH = 720;
3
- var SIZE = 1024 * 64;
3
+ var SIZE = 1024 * 512;
4
4
  var COUNT = 100;
5
5
  var logIndex = 0;
6
6
  var logs = [];
@@ -45,40 +45,43 @@ function getLogs(startTime, count, logId) {
45
45
  return sliceLogs(0, count, logId);
46
46
  }
47
47
 
48
- module.exports = function init(proxy) {
49
- proxy.addLog = function set(log) {
50
- if (!log) {
51
- return;
52
- }
48
+ function getLogIndex() {
49
+ ++logIndex;
50
+ if (logIndex > 999999) {
51
+ logIndex = 0;
52
+ }
53
+ return logIndex;
54
+ }
53
55
 
54
- var now = Date.now();
55
- var text = log.text;
56
- if (text == null) {
57
- text = '';
58
- } else if (typeof text != 'string') {
59
- text += '';
60
- }
61
- var overflow = text.length - SIZE;
62
- logs.push({
63
- id: now + '-' + ++logIndex,
64
- logId: log.id,
65
- date: (log.t && parseInt(log.t, 10)) || now,
66
- level: LEVEL_RE.test(log.level)
56
+ exports.addLog = function set(log) {
57
+ if (!log) {
58
+ return;
59
+ }
60
+
61
+ var now = Date.now();
62
+ var text = log.text;
63
+ if (text == null) {
64
+ text = '';
65
+ } else if (typeof text != 'string') {
66
+ text += '';
67
+ }
68
+ var overflow = text.length - SIZE;
69
+ logs.push({
70
+ id: now + '-' + getLogIndex(),
71
+ logId: log.id,
72
+ date: (log.t && parseInt(log.t, 10)) || now,
73
+ level: LEVEL_RE.test(log.level)
67
74
  ? log.level
68
75
  : 'info',
69
- text: overflow > 9 ? text.substring(0, SIZE) + '...(' + overflow + ')' : text
70
- });
71
- var len = logs.length;
72
- if (len > MAX_LENGTH) {
73
- logs = logs.slice(len - MIN_LENGTH, len);
74
- if (logIndex > 100000) {
75
- logIndex = 0;
76
- }
77
- }
78
- };
79
- proxy.getLogs = getLogs;
80
- proxy.getLatestId = function () {
81
- var last = logs[logs.length - 1];
82
- return last && last.id;
83
- };
76
+ text: overflow > 9 ? text.substring(0, SIZE) + '...(' + overflow + ')' : text
77
+ });
78
+ var len = logs.length;
79
+ if (len > MAX_LENGTH) {
80
+ logs = logs.slice(len - MIN_LENGTH, len);
81
+ }
82
+ };
83
+ exports.getLogs = getLogs;
84
+ exports.getLatestId = function () {
85
+ var last = logs[logs.length - 1];
86
+ return last && last.id;
84
87
  };
@@ -170,9 +170,7 @@ var saveData = function(type, data) {
170
170
  method: 'POST',
171
171
  url: 'http://127.0.0.1:' + curOptions.port + '/cgi-bin/service/save',
172
172
  strictMode: true,
173
- headers: {
174
- 'content-type': 'application/json'
175
- },
173
+ headers: { 'content-type': 'application/json' },
176
174
  body: body
177
175
  }, function (err) {
178
176
  if (err) {
@@ -213,7 +211,7 @@ function sendData() {
213
211
  });
214
212
  }
215
213
 
216
- process.nextTick(sendData);
214
+ process.on('whistleStarted', sendData);
217
215
 
218
216
  module.exports = loadService;
219
217
 
@@ -16,6 +16,7 @@ var composer = require('./composer');
16
16
  var handleComposeData = require('./compose-data');
17
17
  var Limiter = require('async-limiter');
18
18
  var common = require('../util/common');
19
+ var logger = require('./console-log');
19
20
 
20
21
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
21
22
 
@@ -26,21 +27,19 @@ var forwardRequest = dataCenter.forwardRequest;
26
27
 
27
28
  var INSTALL_SCRIPT = path.join(__dirname, 'install.js');
28
29
  var installChild;
29
-
30
+ var LOG_RE = /^(\d+)\r(fatal|error|warn|info|debug|log)\r([^\s]*)\r/;
30
31
  var TEMP_PLUGINS_PATH = path.join(common.getWhistlePath(), '.temp_plugins');
31
32
  var SESSIONS_FILE_RE = /\.(txt|json|saz)$/i;
32
33
  var limiter = new Limiter({ concurrency: 10 });
33
34
  var MAX_PLUGINS = 10;
34
- var TEMP_FILES_PATH;
35
+ var TEMP_FILES_PATH = common.TEMP_FILES_PATH;
35
36
  var SAVED_SESSIONS_PATH;
36
37
  var LIMIT_SIZE = 1024 * 1024 * 128;
37
38
  var MAX_TEMP_SIZE = 1024 * 1024 * 12;
38
39
  var MAX_PLUGIN_SIZE = MAX_TEMP_SIZE;
39
40
  var MAX_TEMP_PLUGINS_AGE = 1000 * 60 * 10; // 10 minutes
40
- var TEMP_FILE_RE = /^[\da-f]{64}$/;
41
41
  var CR = '\r';
42
42
  var jsonParser = bodyParser.json({ limit: LIMIT_SIZE });
43
- var urlencodedParser = bodyParser.urlencoded({ extended: true, limit: '1mb'});
44
43
  var PLUGIN_SEP = /\s*,\s*/;
45
44
  var config;
46
45
  var curWhistleId;
@@ -48,7 +47,6 @@ var hasWhistleToken;
48
47
  var whistleIdFile;
49
48
  var storage = multer.memoryStorage();
50
49
  var INVALID_NAME_RE = /[\u001e\u001f\u200e\u200f\u200d\u200c\u202a\u202d\u202e\u202c\u206e\u206f\u206b\u206a\u206d\u206c'<>:"\\/|?*]+/g;
51
- var SPACE_RE = /\s+/g;
52
50
  var upload = multer({
53
51
  storage: storage,
54
52
  fieldSize: LIMIT_SIZE
@@ -67,6 +65,19 @@ var ensureTempPluginsDir = function() {
67
65
 
68
66
  ensureTempPluginsDir();
69
67
 
68
+ function parseLog(log) {
69
+ if (!log || typeof log !== 'string') {
70
+ return;
71
+ }
72
+ var match = LOG_RE.exec(log);
73
+ return match && {
74
+ t: match[1],
75
+ level: match[2],
76
+ id: match[3],
77
+ text: log.substring(match[0].length)
78
+ };
79
+ }
80
+
70
81
  process.on('data', function(data) {
71
82
  if (data) {
72
83
  if (data.type === 'whistleId') {
@@ -79,7 +90,7 @@ process.on('data', function(data) {
79
90
  });
80
91
 
81
92
  function getFilename(filename, time, count) {
82
- filename = common.isString(filename) ? filename.replace(INVALID_NAME_RE, '').replace(SPACE_RE, ' ').substring(0, 64).trim() : '';
93
+ filename = common.isString(filename) ? filename.replace(INVALID_NAME_RE, '').replace(/\s+/g, ' ').substring(0, 64).trim() : '';
83
94
  time = time || Date.now();
84
95
  return common.getMonth(time) + '/' + filename + '_' + count + '_' + time;
85
96
  }
@@ -96,7 +107,7 @@ function saveSessions(data, cb) {
96
107
  return cb(err);
97
108
  }
98
109
  var filename = getFilename(data.filename, 0, count);
99
- util.writeFile(path.join(SAVED_SESSIONS_PATH, filename), buf, function(e) {
110
+ common.writeFile(path.join(SAVED_SESSIONS_PATH, filename), buf, function(e) {
100
111
  !e && saveData({ type: 'savedData', filename: filename, buffer: buf } );
101
112
  cb(e);
102
113
  });
@@ -155,7 +166,8 @@ function getTempFiles(list, cb) {
155
166
  }
156
167
  };
157
168
  list = list.forEach(function(filename) {
158
- if (!TEMP_FILE_RE.test(filename)) {
169
+ filename = common.getTempFile(filename);
170
+ if (!filename) {
159
171
  return execCb();
160
172
  }
161
173
  filename = path.join(TEMP_FILES_PATH, filename);
@@ -182,7 +194,7 @@ function writeTempPlugin(plugin, data, cb) {
182
194
  plugin = plugin.split('/').pop();
183
195
  var tempPath = path.join(TEMP_PLUGINS_PATH, plugin);
184
196
  ensureTempPluginsDir();
185
- util.writeFile(tempPath, data, function(e) {
197
+ common.writeFile(tempPath, data, function(e) {
186
198
  cb(e, e ? null : tempPath);
187
199
  });
188
200
  }
@@ -296,6 +308,10 @@ function sessionsHandler() {
296
308
  function abort() {
297
309
  res.destroy();
298
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];
299
315
  next();
300
316
  });
301
317
  app.get('/service/*', forwardRequest);
@@ -336,7 +352,7 @@ function sessionsHandler() {
336
352
  } catch (e) {}
337
353
  });
338
354
  });
339
- app.post('/cgi-bin/plugins/install', urlencodedParser, function(req, res) {
355
+ app.post('/cgi-bin/plugins/install', bodyParser.urlencoded({ extended: true, limit: '1mb'}), function(req, res) {
340
356
  var plugins = req.body.plugins;
341
357
  var clientId = req.body.clientId;
342
358
  plugins = common.isString(plugins) ? plugins.trim().split(PLUGIN_SEP) : null;
@@ -444,8 +460,9 @@ function sessionsHandler() {
444
460
  });
445
461
  }
446
462
  var filename = req.query.filename;
447
- if (TEMP_FILE_RE.test(filename)) {
448
- filename = path.join(TEMP_FILES_PATH, filename);
463
+ var tempFile = common.getTempFile(filename);
464
+ if (tempFile) {
465
+ filename = path.join(TEMP_FILES_PATH, tempFile);
449
466
  }
450
467
  getFile(filename, function(em, data) {
451
468
  if (em) {
@@ -458,7 +475,7 @@ function sessionsHandler() {
458
475
  app.use('/cgi-bin/temp/create',
459
476
  jsonParser,
460
477
  function(req, res) {
461
- util.writeTempFile(TEMP_FILES_PATH, getContent(req.body), function(e, filename) {
478
+ common.writeTempFile(getContent(req.body), function(e, filename) {
462
479
  if (e) {
463
480
  return res.json({ ec: 2, em: e.message });
464
481
  }
@@ -491,8 +508,12 @@ function sessionsHandler() {
491
508
  }
492
509
  }
493
510
  );
511
+ app.use('/cgi-bin/log/set', function(req, _, next) {
512
+ req.headers['content-type'] = 'application/json';
513
+ next();
514
+ });
494
515
  app.use(bodyParser.urlencoded({ extended: true, limit: LIMIT_SIZE }));
495
- app.use(bodyParser.json());
516
+ app.use(jsonParser);
496
517
  app.use('/cgi-bin/sessions/export', function (req, res) {
497
518
  var body = req.body;
498
519
  var type = body.exportFileType;
@@ -509,6 +530,35 @@ function sessionsHandler() {
509
530
  download(body);
510
531
  });
511
532
  });
533
+
534
+ app.use('/cgi-bin/log/set', function(req, res) {
535
+ res.setHeader('content-type', 'image/png');
536
+ if (req.method === 'POST') {
537
+ var list = req.body.list;
538
+ if (Array.isArray(list)) {
539
+ var len = Math.min(list.length, 20);
540
+ for (var i = 0; i < len; i++) {
541
+ logger.addLog(parseLog(list[i]));
542
+ }
543
+ }
544
+ } else {
545
+ logger.addLog(req.query);
546
+ }
547
+ res.end();
548
+ });
549
+ app.use('/cgi-bin/log/get', function(req, res) {
550
+ var query = req.query;
551
+ var startLogTime = query.startLogTime;
552
+ var init = startLogTime == -4;
553
+ var stopRecordConsole = startLogTime == -3;
554
+ var curLogId = logger.getLatestId();
555
+ common.sendGzip(req, res, {
556
+ ec: 0,
557
+ log: init || stopRecordConsole ? [] : logger.getLogs(startLogTime, query.count, query.logId),
558
+ curLogId: stopRecordConsole ? undefined : curLogId,
559
+ lastLogId: init ? (curLogId || -2) : (stopRecordConsole ? curLogId : undefined)
560
+ });
561
+ });
512
562
  return app;
513
563
  }
514
564
 
@@ -564,7 +614,6 @@ function checkWhistleId(cb) {
564
614
  module.exports = function (options, callback) {
565
615
  config = options;
566
616
  whistleIdFile = path.join(options.baseDir, options.storage || '', '.whistle_id');
567
- TEMP_FILES_PATH = options.TEMP_FILES_PATH;
568
617
  SAVED_SESSIONS_PATH = options.SAVED_SESSIONS_PATH;
569
618
  dataCenter.setup(options);
570
619
  composer.setup(options);
@@ -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(descSorter).slice(0, 12).map(function(dir) {
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
- function getFrameId() {
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
@@ -47,8 +47,7 @@ function tunnelProxy(server, proxy, type) {
47
47
  var headers = req.headers;
48
48
  if (headers[config.WEBUI_HEAD]) {
49
49
  delete headers[config.WEBUI_HEAD];
50
- reqSocket.destroy();
51
- return;
50
+ return reqSocket.destroy();
52
51
  }
53
52
  req.fromHttpServer = reqSocket.fromHttpServer = fromHttpServer;
54
53
  req.fromHttpsServer = reqSocket.fromHttpsServer = fromHttpsServer;
@@ -83,8 +82,7 @@ function tunnelProxy(server, proxy, type) {
83
82
  var clientInfo = util.parseClientInfo(req);
84
83
  reqSocket._disabledProxyRules = req._disabledProxyRules;
85
84
  reqSocket.clientIp = req.clientIp = util.getClientIp(req, clientInfo[0]);
86
- reqSocket.clientPort = req.clientPort =
87
- clientInfo[1] || util.getClientPort(req);
85
+ reqSocket.clientPort = req.clientPort = clientInfo[1] || util.getClientPort(req);
88
86
  req._remoteAddr = clientInfo[2] || util.getRemoteAddr(req);
89
87
  req._remotePort = clientInfo[3] || util.getRemotePort(req);
90
88
  delete headers[config.CLIENT_PORT_HEADER];
package/lib/upgrade.js CHANGED
@@ -105,7 +105,7 @@ function upgradeHandler(req, socket) {
105
105
  util.addTunnelData(socket, headers);
106
106
  socket._clientId = util.getComposerClientId(headers);
107
107
  var getBuffer = function (method, newHeaders, path) {
108
- var rawData = (method || 'GET') + ' ' + (path || socket.url || req.url) + ' ' + 'HTTP/1.1';
108
+ var rawData = (method || 'GET') + ' ' + (path || socket.url || req.url) + ' HTTP/1.1';
109
109
  newHeaders = formatHeaders(newHeaders || headers, req.rawHeaders);
110
110
  if (newHeaders = getRawHeaders(newHeaders)) {
111
111
  rawData += '\r\n' + newHeaders;
@@ -67,7 +67,81 @@ var PATH_SEP_RE = /[\\/]/;
67
67
  var CUR_PATH_RE = /^\.[\\/]/;
68
68
  var CLIENT_PORT_HEADER = 'x-whistle-client-port';
69
69
  var ALLOW_ACK = 'x-whistle-allow-tunnel-ack';
70
+ var PLUS_RE = /\+/g;
71
+ var TOKEN = '\r\u0000\n\u0003\r';
72
+ var PROP_SEP_RE = /(\\*)([|&]|\\[stnrfv])/g;
73
+ var TEMP_FILE_RE = /^(?:\/?temp\/)?([\da-f]{64})(?:\.[\w.-]+)?$/;
74
+ var TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
70
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];
82
+
83
+ };
84
+
85
+ var PROPS_CHAR = {
86
+ s: ' ',
87
+ t: '\t',
88
+ n: '\n',
89
+ r: '\r',
90
+ f: '\f',
91
+ v: '\v'
92
+ };
93
+
94
+ function replaceToken(s, val) {
95
+ return s.split(TOKEN).join(val);
96
+ }
97
+
98
+ exports.parseProps = function(str) {
99
+ return str.replace(PROP_SEP_RE, function(_, slash, val) {
100
+ var isChar = val.length > 1;
101
+ if (isChar) {
102
+ slash += '/';
103
+ val = val.substring(1);
104
+ } else if (!slash) {
105
+ return TOKEN;
106
+ }
107
+ var len = slash.length;
108
+ slash = slash.substring(0, Math.floor(len / 2));
109
+ if (isChar) {
110
+ return slash + (len % 2 ? PROPS_CHAR[val] : val);
111
+ }
112
+ return slash + (len % 2 ? val : TOKEN);
113
+ }).split(TOKEN);
114
+ };
115
+
116
+ var decoder = {
117
+ decodeURIComponent: function (s) {
118
+ s = replaceToken(s, '+');
119
+ return qs.unescape(s);
120
+ }
121
+ };
122
+ var rawDecoder = {
123
+ decodeURIComponent: function (s) {
124
+ return replaceToken(s, '+');
125
+ }
126
+ };
127
+ var rawDecoder2 = {
128
+ decodeURIComponent: function (s) {
129
+ return s;
130
+ }
131
+ };
132
+
133
+ exports.parseQuery = function (str, sep, eq, escape) {
134
+ try {
135
+ if (str.indexOf('+') === -1 || str.indexOf(TOKEN) !== -1) {
136
+ return qs.parse(str, sep, eq, escape ? rawDecoder2 : undefined);
137
+ }
138
+ str = str.replace(PLUS_RE, TOKEN);
139
+ return qs.parse(str, sep, eq, escape ? rawDecoder : decoder);
140
+ } catch (e) {}
141
+ return '';
142
+ };
143
+
144
+ exports.TEMP_FILES_PATH = TEMP_FILES_PATH;
71
145
  exports.ALLOW_ACK = ALLOW_ACK;
72
146
  exports.CONN_TIMEOUT = CONN_TIMEOUT;
73
147
  exports.TGZ_FILE_NAME_RE = /^(?:[\w.~-]+-)?whistle\.[a-z\d_-]+-\d+\.\d+\.\d+[\w.-]*\.(?:tgz|tar(?:\.gz)?)$/;
@@ -414,14 +488,8 @@ exports.isConnect = function(options) {
414
488
  );
415
489
  };
416
490
 
417
- var NO_PROTO_RE = /[^a-zA-Z0-9.-]/;
418
-
419
491
  function hasProtocol(url) {
420
- var index = isString(url) ? url.indexOf('://') : -1;
421
- if (index <= 0) {
422
- return false;
423
- }
424
- return !NO_PROTO_RE.test(url.substring(0, index));
492
+ return /^[a-zA-Z0-9.-]+:\/\//.test(url);
425
493
  }
426
494
 
427
495
  function setProtocol(url, isHttps) {
@@ -894,7 +962,6 @@ var DIG_RE = /^([+-]?)([1-9]\d{0,15})$/;
894
962
  var NUM_RE = /^(?:0|[1-9]\d*)$/;
895
963
  var ARR_RE = /\[(0|[1-8]\d{0,15}|9\d{0,14})\]$/;
896
964
  var DOT_RE = /(\\+)\./g;
897
- var CR_RE = /\r/g;
898
965
 
899
966
  function isString(str) {
900
967
  return str && typeof str === 'string';
@@ -963,12 +1030,6 @@ function parseKey(key) {
963
1030
  return list;
964
1031
  }
965
1032
 
966
- function replaceDot(_, slash) {
967
- var len = slash.length;
968
- slash = slash.substring(0, Math.floor(len / 2));
969
- return slash + (len % 2 ? '\r' : '.');
970
- }
971
-
972
1033
  function parseKeys(key) {
973
1034
  if (!isString(key)) {
974
1035
  return key;
@@ -977,13 +1038,17 @@ function parseKeys(key) {
977
1038
  if (key.indexOf('.') === -1) {
978
1039
  return parseKey(key);
979
1040
  }
980
- key = key.replace(DOT_RE, replaceDot);
1041
+ key = key.replace(DOT_RE, function (_, slash) {
1042
+ var len = slash.length;
1043
+ slash = slash.substring(0, Math.floor(len / 2));
1044
+ return slash + (len % 2 ? TOKEN : '.');
1045
+ });
981
1046
  if (key.indexOf('.') === -1) {
982
- return parseKey(key.replace(CR_RE, '.'));
1047
+ return parseKey(replaceToken(key, '.'));
983
1048
  }
984
1049
  var result = [];
985
1050
  key.split('.').forEach(function(k) {
986
- k = parseKey(k.trim().replace(CR_RE, '.'));
1051
+ k = parseKey(replaceToken(k.trim(), '.'));
987
1052
  if (Array.isArray(k)) {
988
1053
  result = result.concat(k);
989
1054
  } else {
@@ -1937,3 +2002,36 @@ exports.getSessionInfo = function(headers) {
1937
2002
  result._ = rawResult;
1938
2003
  return result;
1939
2004
  };
2005
+
2006
+ function writeRetry(filepath, data, callback, retry) {
2007
+ fse.outputFile(filepath, data, function(e) {
2008
+ if (!e || retry) {
2009
+ return callback(e);
2010
+ }
2011
+ writeRetry(filepath, data, callback, true);
2012
+ });
2013
+ }
2014
+
2015
+ function writeFile(filepath, data, callback) {
2016
+ getStat(filepath, function(_, stat) {
2017
+ if (!stat || !stat.isFile()) {
2018
+ return writeRetry(filepath, data, callback);
2019
+ }
2020
+ callback();
2021
+ });
2022
+ }
2023
+
2024
+ exports.writeFile = writeFile;
2025
+
2026
+ function getHexHash(ctn, key) {
2027
+ return crypto
2028
+ .createHmac('sha256', key || 'whistle_temp_files')
2029
+ .update(ctn || '').digest('hex').toLowerCase();
2030
+ }
2031
+
2032
+ exports.writeTempFile = function (value, callback) {
2033
+ var filename = getHexHash(value);
2034
+ writeFile(path.join(TEMP_FILES_PATH, filename), value, function(err) {
2035
+ callback(err, filename);
2036
+ });
2037
+ };