utility.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. /*!
  2. * utility - lib/utility.js
  3. *
  4. * Copyright(c) 2012 - 2014 fengmk2 <fengmk2@gmail.com>
  5. * MIT Licensed
  6. */
  7. "use strict";
  8. /**
  9. * Module dependencies.
  10. */
  11. var crypto = require('crypto');
  12. var address = require('address');
  13. /**
  14. * A empty function.
  15. *
  16. * @return {Function}
  17. * @public
  18. */
  19. exports.noop = function () {};
  20. function sortObject(o) {
  21. if (!o || Array.isArray(o) || typeof o !== 'object') {
  22. return o;
  23. }
  24. var keys = Object.keys(o);
  25. keys.sort();
  26. var values = [];
  27. for (var i = 0; i < keys.length; i++) {
  28. var k = keys[i];
  29. values.push([k, sortObject(o[k])]);
  30. }
  31. return values;
  32. }
  33. /**
  34. * hash
  35. *
  36. * @param {String} method hash method, e.g.: 'md5', 'sha1'
  37. * @param {String|Buffer} s
  38. * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'.
  39. * @return {String} md5 hash string
  40. * @public
  41. */
  42. exports.hash = function hash(method, s, format) {
  43. var sum = crypto.createHash(method);
  44. var isBuffer = Buffer.isBuffer(s);
  45. if (!isBuffer && typeof s === 'object') {
  46. s = JSON.stringify(sortObject(s));
  47. }
  48. sum.update(s, isBuffer ? 'binary' : 'utf8');
  49. return sum.digest(format || 'hex');
  50. };
  51. /**
  52. * md5 hash
  53. *
  54. * @param {String|Buffer} s
  55. * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'.
  56. * @return {String} md5 hash string
  57. * @public
  58. */
  59. exports.md5 = function (s, format) {
  60. return exports.hash('md5', s, format);
  61. };
  62. /**
  63. * sha1 hash
  64. *
  65. * @param {String|Buffer} s
  66. * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'.
  67. * @return {String} sha1 hash string
  68. * @public
  69. */
  70. exports.sha1 = function (s, format) {
  71. return exports.hash('sha1', s, format);
  72. };
  73. /**
  74. * HMAC algorithm.
  75. *
  76. * Equal bash:
  77. *
  78. * ```bash
  79. * $ echo -n "$data" | openssl dgst -binary -$algorithm -hmac "$key" | openssl $encoding
  80. * ```
  81. *
  82. * @param {String} algorithm, dependent on the available algorithms supported by the version of OpenSSL on the platform.
  83. * Examples are 'sha1', 'md5', 'sha256', 'sha512', etc.
  84. * On recent releases, `openssl list-message-digest-algorithms` will display the available digest algorithms.
  85. * @param {String} key, the hmac key to be used.
  86. * @param {String|Buffer} data, content string.
  87. * @param {String} [encoding='base64']
  88. * @return {String} digest string.
  89. */
  90. exports.hmac = function (algorithm, key, data, encoding) {
  91. encoding = encoding || 'base64';
  92. var hmac = crypto.createHmac(algorithm, key);
  93. hmac.update(data, Buffer.isBuffer(data) ? 'binary' : 'utf8');
  94. return hmac.digest(encoding);
  95. };
  96. /**
  97. * Base64 encode string.
  98. *
  99. * @param {String|Buffer} s
  100. * @param {Boolean} [urlsafe=false] Encode string s using a URL-safe alphabet,
  101. * which substitutes - instead of + and _ instead of / in the standard Base64 alphabet.
  102. * @return {String} base64 encode format string.
  103. */
  104. exports.base64encode = function (s, urlsafe) {
  105. if (!Buffer.isBuffer(s)) {
  106. s = new Buffer(s);
  107. }
  108. var encode = s.toString('base64');
  109. if (urlsafe) {
  110. encode = encode.replace(/\+/g, '-').replace(/\//g, '_');
  111. }
  112. return encode;
  113. };
  114. /**
  115. * Base64 string decode.
  116. *
  117. * @param {String} encode, base64 encoding string.
  118. * @param {Boolean} [urlsafe=false] Decode string s using a URL-safe alphabet,
  119. * which substitutes - instead of + and _ instead of / in the standard Base64 alphabet.
  120. * @return {String} plain text.
  121. */
  122. exports.base64decode = function (encode, urlsafe) {
  123. if (urlsafe) {
  124. encode = encode.replace(/\-/g, '+').replace(/_/g, '/');
  125. }
  126. encode = new Buffer(encode, 'base64');
  127. return encode.toString();
  128. };
  129. /**
  130. * Escape the given string of `html`.
  131. *
  132. * @param {String} html
  133. * @return {String}
  134. * @public
  135. */
  136. exports.escape = function (html) {
  137. return String(html)
  138. .replace(/&(?!\w+;)/g, '&amp;')
  139. .replace(/</g, '&lt;')
  140. .replace(/>/g, '&gt;')
  141. .replace(/"/g, '&quot;');
  142. };
  143. /**
  144. * Array random slice with items count.
  145. * @param {Array} arr
  146. * @param {Number} num, number of sub items.
  147. * @return {Array}
  148. */
  149. exports.randomSlice = function (arr, num) {
  150. if (!num || num >= arr.length) {
  151. return arr.slice();
  152. }
  153. var index = Math.floor(Math.random() * arr.length);
  154. var a = [];
  155. for (var i = 0, j = index; i < num; i++) {
  156. a.push(arr[j++]);
  157. if (j === arr.length) {
  158. j = 0;
  159. }
  160. }
  161. return a;
  162. };
  163. /**
  164. * Safe encodeURIComponent, won't throw any error.
  165. * If `encodeURIComponent` error happen, just return the original value.
  166. *
  167. * @param {String} text
  168. * @return {String} URL encode string.
  169. */
  170. exports.encodeURIComponent = function (text) {
  171. try {
  172. return encodeURIComponent(text);
  173. } catch (e) {
  174. return text;
  175. }
  176. };
  177. /**
  178. * Safe decodeURIComponent, won't throw any error.
  179. * If `decodeURIComponent` error happen, just return the original value.
  180. *
  181. * @param {String} encodeText
  182. * @return {String} URL decode original string.
  183. */
  184. exports.decodeURIComponent = function (encodeText) {
  185. try {
  186. return decodeURIComponent(encodeText);
  187. } catch (e) {
  188. return encodeText;
  189. }
  190. };
  191. var MONTHS = [
  192. 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
  193. ];
  194. // only set once.
  195. var TIMEZONE = ' ';
  196. var _hourOffset = parseInt(-(new Date().getTimezoneOffset()) / 60, 10);
  197. if (_hourOffset >= 0) {
  198. TIMEZONE += '+';
  199. } else {
  200. TIMEZONE += '-';
  201. }
  202. _hourOffset = Math.abs(_hourOffset);
  203. if (_hourOffset < 10) {
  204. _hourOffset = '0' + _hourOffset;
  205. }
  206. TIMEZONE += _hourOffset + '00';
  207. /**
  208. * Access log format date. format: `moment().format('DD/MMM/YYYY:HH:mm:ss ZZ')`
  209. *
  210. * @return {String}
  211. */
  212. exports.accessLogDate = function (d) {
  213. // 16/Apr/2013:16:40:09 +0800
  214. d = d || new Date();
  215. var date = d.getDate();
  216. if (date < 10) {
  217. date = '0' + date;
  218. }
  219. var hours = d.getHours();
  220. if (hours < 10) {
  221. hours = '0' + hours;
  222. }
  223. var mintues = d.getMinutes();
  224. if (mintues < 10) {
  225. mintues = '0' + mintues;
  226. }
  227. var seconds = d.getSeconds();
  228. if (seconds < 10) {
  229. seconds = '0' + seconds;
  230. }
  231. return date + '/' + MONTHS[d.getMonth()] + '/' + d.getFullYear() +
  232. ':' + hours + ':' + mintues + ':' + seconds + TIMEZONE;
  233. };
  234. /**
  235. * Normal log format date. format: `moment().format('YYYY-MM-DD HH:mm:ss.SSS')`
  236. *
  237. * @return {String}
  238. */
  239. exports.logDate = exports.YYYYMMDDHHmmssSSS = function (d, msSep) {
  240. if (typeof d === 'string') {
  241. // logDate(msSep)
  242. msSep = d;
  243. d = new Date();
  244. } else {
  245. // logDate(d, msSep)
  246. d = d || new Date();
  247. }
  248. var date = d.getDate();
  249. if (date < 10) {
  250. date = '0' + date;
  251. }
  252. var month = d.getMonth() + 1;
  253. if (month < 10) {
  254. month = '0' + month;
  255. }
  256. var hours = d.getHours();
  257. if (hours < 10) {
  258. hours = '0' + hours;
  259. }
  260. var mintues = d.getMinutes();
  261. if (mintues < 10) {
  262. mintues = '0' + mintues;
  263. }
  264. var seconds = d.getSeconds();
  265. if (seconds < 10) {
  266. seconds = '0' + seconds;
  267. }
  268. var milliseconds = d.getMilliseconds();
  269. if (milliseconds < 10) {
  270. milliseconds = '00' + milliseconds;
  271. } else if (milliseconds < 100) {
  272. milliseconds = '0' + milliseconds;
  273. }
  274. return d.getFullYear() + '-' + month + '-' + date + ' ' +
  275. hours + ':' + mintues + ':' + seconds + (msSep || '.') + milliseconds;
  276. };
  277. /**
  278. * `moment().format('YYYY-MM-DD HH:mm:ss')` format date string.
  279. *
  280. * @return {String}
  281. */
  282. exports.YYYYMMDDHHmmss = function (d, options) {
  283. d = d || new Date();
  284. var dateSep = '-';
  285. var timeSep = ':';
  286. if (options) {
  287. if (options.dateSep) {
  288. dateSep = options.dateSep;
  289. }
  290. if (options.timeSep) {
  291. timeSep = options.timeSep;
  292. }
  293. }
  294. var date = d.getDate();
  295. if (date < 10) {
  296. date = '0' + date;
  297. }
  298. var month = d.getMonth() + 1;
  299. if (month < 10) {
  300. month = '0' + month;
  301. }
  302. var hours = d.getHours();
  303. if (hours < 10) {
  304. hours = '0' + hours;
  305. }
  306. var mintues = d.getMinutes();
  307. if (mintues < 10) {
  308. mintues = '0' + mintues;
  309. }
  310. var seconds = d.getSeconds();
  311. if (seconds < 10) {
  312. seconds = '0' + seconds;
  313. }
  314. return d.getFullYear() + dateSep + month + dateSep + date + ' ' +
  315. hours + timeSep + mintues + timeSep + seconds;
  316. };
  317. /**
  318. * `moment().format('YYYY-MM-DD')` format date string.
  319. *
  320. * @return {String}
  321. */
  322. exports.YYYYMMDD = function YYYYMMDD(d, sep) {
  323. if (typeof d === 'string') {
  324. // YYYYMMDD(sep)
  325. sep = d;
  326. d = new Date();
  327. } else {
  328. // YYYYMMDD(d, sep)
  329. d = d || new Date();
  330. if (typeof sep !== 'string') {
  331. sep = '-';
  332. }
  333. }
  334. var date = d.getDate();
  335. if (date < 10) {
  336. date = '0' + date;
  337. }
  338. var month = d.getMonth() + 1;
  339. if (month < 10) {
  340. month = '0' + month;
  341. }
  342. return d.getFullYear() + sep + month + sep + date;
  343. };
  344. /**
  345. * return datetime struct.
  346. *
  347. * @return {Object} date
  348. * - {Number} YYYYMMDD, 20130401
  349. * - {Number} H, 0, 1, 9, 12, 23
  350. */
  351. exports.datestruct = function (now) {
  352. now = now || new Date();
  353. return {
  354. YYYYMMDD: now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate(),
  355. H: now.getHours()
  356. };
  357. };
  358. var _showWarnning = false;
  359. /**
  360. * Get current machine IPv4
  361. *
  362. * @param {String} [interfaceName] interface name, default is 'eth' on linux, 'en' on mac os.
  363. * @return {String} IP address
  364. */
  365. exports.getIP = exports.getIPv4 = function (interfaceName) {
  366. if (!_showWarnning) {
  367. _showWarnning = true;
  368. console.warn('[WARNNING] getIP() remove, PLEASE use `https://github.com/fengmk2/address` module instead');
  369. }
  370. return address.ip(interfaceName);
  371. };
  372. /**
  373. * Get current machine IPv6
  374. *
  375. * @param {String} [interfaceName] interface name, default is 'eth' on linux, 'en' on mac os.
  376. * @return {String} IP address
  377. */
  378. exports.getIPv6 = function (interfaceName) {
  379. return address.ipv6(interfaceName);
  380. };
  381. /**
  382. * Get a function parameter's names.
  383. *
  384. * @param {Function} func
  385. * @param {Boolean} [useCache], default is true
  386. * @return {Array} names
  387. */
  388. exports.getParamNames = function (func, cache) {
  389. cache = cache !== false;
  390. if (cache && func.__cache_names) {
  391. return func.__cache_names;
  392. }
  393. var str = func.toString();
  394. var names = str.slice(str.indexOf('(') + 1, str.indexOf(')')).match(/([^\s,]+)/g) || [];
  395. func.__cache_names = names;
  396. return names;
  397. };
  398. // http://www.2ality.com/2013/10/safe-integers.html
  399. // http://es6.ruanyifeng.com/#docs/number
  400. exports.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;
  401. exports.MIN_SAFE_INTEGER = -exports.MAX_SAFE_INTEGER;
  402. var MAX_SAFE_INTEGER_STR = exports.MAX_SAFE_INTEGER_STR = String(exports.MAX_SAFE_INTEGER);
  403. var MAX_SAFE_INTEGER_STR_LENGTH = MAX_SAFE_INTEGER_STR.length;
  404. /**
  405. * Detect a number string can safe convert to Javascript Number.
  406. *
  407. * @param {String} s number format string, like `"123"`, `"-1000123123123123123123"`
  408. * @return {Boolean}
  409. */
  410. exports.isSafeNumberString = function (s) {
  411. if (s[0] === '-') {
  412. s = s.substring(1);
  413. }
  414. if (s.length < MAX_SAFE_INTEGER_STR_LENGTH || (s.length === MAX_SAFE_INTEGER_STR_LENGTH && s <= MAX_SAFE_INTEGER_STR)) {
  415. return true;
  416. }
  417. return false;
  418. };
  419. /**
  420. * Convert string to Number if string in safe Number scope.
  421. *
  422. * @param {String} s number format string.
  423. * @return {Number|String} success will return Number, otherise return the original string.
  424. */
  425. exports.toSafeNumber = function (s) {
  426. if (typeof s === 'number') {
  427. return s;
  428. }
  429. return exports.isSafeNumberString(s) ? Number(s) : s;
  430. };
  431. /**
  432. * Get Unix's timestamp in seconds.
  433. * @return {Number}
  434. */
  435. exports.timestamp = function (t) {
  436. if (t) {
  437. var v = t;
  438. if (typeof v === 'string') {
  439. v = Number(v);
  440. }
  441. if (String(t).length === 10) {
  442. v *= 1000;
  443. }
  444. return new Date(v);
  445. }
  446. return Math.round(Date.now() / 1000);
  447. };
  448. var _setImmediate = typeof setImmediate === 'function' ? setImmediate : process.nextTick;
  449. exports.setImmediate = function (callback) {
  450. _setImmediate(callback);
  451. };
  452. exports.randomString = function (length, charSet) {
  453. var result = [];
  454. length = length || 16;
  455. charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  456. while (length--) {
  457. result.push(charSet[Math.floor(Math.random() * charSet.length)]);
  458. }
  459. return result.join('');
  460. };
  461. exports.has = function (obj, prop) {
  462. return Object.prototype.hasOwnProperty.call(obj, prop);
  463. };
  464. /**
  465. * generate a real map object, no constructor, no __proto__
  466. * @param {Object} [obj], init object, optional
  467. * @return {Object}
  468. */
  469. exports.map = function (obj) {
  470. var map = Object.create(null);
  471. if (!obj) {
  472. return map;
  473. }
  474. for (var key in obj) {
  475. map[key] = obj[key];
  476. }
  477. return map;
  478. };