/*! * utility - lib/utility.js * * Copyright(c) 2012 - 2014 fengmk2 * MIT Licensed */ "use strict"; /** * Module dependencies. */ var crypto = require('crypto'); var address = require('address'); /** * A empty function. * * @return {Function} * @public */ exports.noop = function () {}; function sortObject(o) { if (!o || Array.isArray(o) || typeof o !== 'object') { return o; } var keys = Object.keys(o); keys.sort(); var values = []; for (var i = 0; i < keys.length; i++) { var k = keys[i]; values.push([k, sortObject(o[k])]); } return values; } /** * hash * * @param {String} method hash method, e.g.: 'md5', 'sha1' * @param {String|Buffer} s * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'. * @return {String} md5 hash string * @public */ exports.hash = function hash(method, s, format) { var sum = crypto.createHash(method); var isBuffer = Buffer.isBuffer(s); if (!isBuffer && typeof s === 'object') { s = JSON.stringify(sortObject(s)); } sum.update(s, isBuffer ? 'binary' : 'utf8'); return sum.digest(format || 'hex'); }; /** * md5 hash * * @param {String|Buffer} s * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'. * @return {String} md5 hash string * @public */ exports.md5 = function (s, format) { return exports.hash('md5', s, format); }; /** * sha1 hash * * @param {String|Buffer} s * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'. * @return {String} sha1 hash string * @public */ exports.sha1 = function (s, format) { return exports.hash('sha1', s, format); }; /** * HMAC algorithm. * * Equal bash: * * ```bash * $ echo -n "$data" | openssl dgst -binary -$algorithm -hmac "$key" | openssl $encoding * ``` * * @param {String} algorithm, dependent on the available algorithms supported by the version of OpenSSL on the platform. * Examples are 'sha1', 'md5', 'sha256', 'sha512', etc. * On recent releases, `openssl list-message-digest-algorithms` will display the available digest algorithms. * @param {String} key, the hmac key to be used. * @param {String|Buffer} data, content string. * @param {String} [encoding='base64'] * @return {String} digest string. */ exports.hmac = function (algorithm, key, data, encoding) { encoding = encoding || 'base64'; var hmac = crypto.createHmac(algorithm, key); hmac.update(data, Buffer.isBuffer(data) ? 'binary' : 'utf8'); return hmac.digest(encoding); }; /** * Base64 encode string. * * @param {String|Buffer} s * @param {Boolean} [urlsafe=false] Encode string s using a URL-safe alphabet, * which substitutes - instead of + and _ instead of / in the standard Base64 alphabet. * @return {String} base64 encode format string. */ exports.base64encode = function (s, urlsafe) { if (!Buffer.isBuffer(s)) { s = new Buffer(s); } var encode = s.toString('base64'); if (urlsafe) { encode = encode.replace(/\+/g, '-').replace(/\//g, '_'); } return encode; }; /** * Base64 string decode. * * @param {String} encode, base64 encoding string. * @param {Boolean} [urlsafe=false] Decode string s using a URL-safe alphabet, * which substitutes - instead of + and _ instead of / in the standard Base64 alphabet. * @return {String} plain text. */ exports.base64decode = function (encode, urlsafe) { if (urlsafe) { encode = encode.replace(/\-/g, '+').replace(/_/g, '/'); } encode = new Buffer(encode, 'base64'); return encode.toString(); }; /** * Escape the given string of `html`. * * @param {String} html * @return {String} * @public */ exports.escape = function (html) { return String(html) .replace(/&(?!\w+;)/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }; /** * Array random slice with items count. * @param {Array} arr * @param {Number} num, number of sub items. * @return {Array} */ exports.randomSlice = function (arr, num) { if (!num || num >= arr.length) { return arr.slice(); } var index = Math.floor(Math.random() * arr.length); var a = []; for (var i = 0, j = index; i < num; i++) { a.push(arr[j++]); if (j === arr.length) { j = 0; } } return a; }; /** * Safe encodeURIComponent, won't throw any error. * If `encodeURIComponent` error happen, just return the original value. * * @param {String} text * @return {String} URL encode string. */ exports.encodeURIComponent = function (text) { try { return encodeURIComponent(text); } catch (e) { return text; } }; /** * Safe decodeURIComponent, won't throw any error. * If `decodeURIComponent` error happen, just return the original value. * * @param {String} encodeText * @return {String} URL decode original string. */ exports.decodeURIComponent = function (encodeText) { try { return decodeURIComponent(encodeText); } catch (e) { return encodeText; } }; var MONTHS = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; // only set once. var TIMEZONE = ' '; var _hourOffset = parseInt(-(new Date().getTimezoneOffset()) / 60, 10); if (_hourOffset >= 0) { TIMEZONE += '+'; } else { TIMEZONE += '-'; } _hourOffset = Math.abs(_hourOffset); if (_hourOffset < 10) { _hourOffset = '0' + _hourOffset; } TIMEZONE += _hourOffset + '00'; /** * Access log format date. format: `moment().format('DD/MMM/YYYY:HH:mm:ss ZZ')` * * @return {String} */ exports.accessLogDate = function (d) { // 16/Apr/2013:16:40:09 +0800 d = d || new Date(); var date = d.getDate(); if (date < 10) { date = '0' + date; } var hours = d.getHours(); if (hours < 10) { hours = '0' + hours; } var mintues = d.getMinutes(); if (mintues < 10) { mintues = '0' + mintues; } var seconds = d.getSeconds(); if (seconds < 10) { seconds = '0' + seconds; } return date + '/' + MONTHS[d.getMonth()] + '/' + d.getFullYear() + ':' + hours + ':' + mintues + ':' + seconds + TIMEZONE; }; /** * Normal log format date. format: `moment().format('YYYY-MM-DD HH:mm:ss.SSS')` * * @return {String} */ exports.logDate = exports.YYYYMMDDHHmmssSSS = function (d, msSep) { if (typeof d === 'string') { // logDate(msSep) msSep = d; d = new Date(); } else { // logDate(d, msSep) d = d || new Date(); } var date = d.getDate(); if (date < 10) { date = '0' + date; } var month = d.getMonth() + 1; if (month < 10) { month = '0' + month; } var hours = d.getHours(); if (hours < 10) { hours = '0' + hours; } var mintues = d.getMinutes(); if (mintues < 10) { mintues = '0' + mintues; } var seconds = d.getSeconds(); if (seconds < 10) { seconds = '0' + seconds; } var milliseconds = d.getMilliseconds(); if (milliseconds < 10) { milliseconds = '00' + milliseconds; } else if (milliseconds < 100) { milliseconds = '0' + milliseconds; } return d.getFullYear() + '-' + month + '-' + date + ' ' + hours + ':' + mintues + ':' + seconds + (msSep || '.') + milliseconds; }; /** * `moment().format('YYYY-MM-DD HH:mm:ss')` format date string. * * @return {String} */ exports.YYYYMMDDHHmmss = function (d, options) { d = d || new Date(); var dateSep = '-'; var timeSep = ':'; if (options) { if (options.dateSep) { dateSep = options.dateSep; } if (options.timeSep) { timeSep = options.timeSep; } } var date = d.getDate(); if (date < 10) { date = '0' + date; } var month = d.getMonth() + 1; if (month < 10) { month = '0' + month; } var hours = d.getHours(); if (hours < 10) { hours = '0' + hours; } var mintues = d.getMinutes(); if (mintues < 10) { mintues = '0' + mintues; } var seconds = d.getSeconds(); if (seconds < 10) { seconds = '0' + seconds; } return d.getFullYear() + dateSep + month + dateSep + date + ' ' + hours + timeSep + mintues + timeSep + seconds; }; /** * `moment().format('YYYY-MM-DD')` format date string. * * @return {String} */ exports.YYYYMMDD = function YYYYMMDD(d, sep) { if (typeof d === 'string') { // YYYYMMDD(sep) sep = d; d = new Date(); } else { // YYYYMMDD(d, sep) d = d || new Date(); if (typeof sep !== 'string') { sep = '-'; } } var date = d.getDate(); if (date < 10) { date = '0' + date; } var month = d.getMonth() + 1; if (month < 10) { month = '0' + month; } return d.getFullYear() + sep + month + sep + date; }; /** * return datetime struct. * * @return {Object} date * - {Number} YYYYMMDD, 20130401 * - {Number} H, 0, 1, 9, 12, 23 */ exports.datestruct = function (now) { now = now || new Date(); return { YYYYMMDD: now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate(), H: now.getHours() }; }; var _showWarnning = false; /** * Get current machine IPv4 * * @param {String} [interfaceName] interface name, default is 'eth' on linux, 'en' on mac os. * @return {String} IP address */ exports.getIP = exports.getIPv4 = function (interfaceName) { if (!_showWarnning) { _showWarnning = true; console.warn('[WARNNING] getIP() remove, PLEASE use `https://github.com/fengmk2/address` module instead'); } return address.ip(interfaceName); }; /** * Get current machine IPv6 * * @param {String} [interfaceName] interface name, default is 'eth' on linux, 'en' on mac os. * @return {String} IP address */ exports.getIPv6 = function (interfaceName) { return address.ipv6(interfaceName); }; /** * Get a function parameter's names. * * @param {Function} func * @param {Boolean} [useCache], default is true * @return {Array} names */ exports.getParamNames = function (func, cache) { cache = cache !== false; if (cache && func.__cache_names) { return func.__cache_names; } var str = func.toString(); var names = str.slice(str.indexOf('(') + 1, str.indexOf(')')).match(/([^\s,]+)/g) || []; func.__cache_names = names; return names; }; // http://www.2ality.com/2013/10/safe-integers.html // http://es6.ruanyifeng.com/#docs/number exports.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1; exports.MIN_SAFE_INTEGER = -exports.MAX_SAFE_INTEGER; var MAX_SAFE_INTEGER_STR = exports.MAX_SAFE_INTEGER_STR = String(exports.MAX_SAFE_INTEGER); var MAX_SAFE_INTEGER_STR_LENGTH = MAX_SAFE_INTEGER_STR.length; /** * Detect a number string can safe convert to Javascript Number. * * @param {String} s number format string, like `"123"`, `"-1000123123123123123123"` * @return {Boolean} */ exports.isSafeNumberString = function (s) { if (s[0] === '-') { s = s.substring(1); } if (s.length < MAX_SAFE_INTEGER_STR_LENGTH || (s.length === MAX_SAFE_INTEGER_STR_LENGTH && s <= MAX_SAFE_INTEGER_STR)) { return true; } return false; }; /** * Convert string to Number if string in safe Number scope. * * @param {String} s number format string. * @return {Number|String} success will return Number, otherise return the original string. */ exports.toSafeNumber = function (s) { if (typeof s === 'number') { return s; } return exports.isSafeNumberString(s) ? Number(s) : s; }; /** * Get Unix's timestamp in seconds. * @return {Number} */ exports.timestamp = function (t) { if (t) { var v = t; if (typeof v === 'string') { v = Number(v); } if (String(t).length === 10) { v *= 1000; } return new Date(v); } return Math.round(Date.now() / 1000); }; var _setImmediate = typeof setImmediate === 'function' ? setImmediate : process.nextTick; exports.setImmediate = function (callback) { _setImmediate(callback); }; exports.randomString = function (length, charSet) { var result = []; length = length || 16; charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; while (length--) { result.push(charSet[Math.floor(Math.random() * charSet.length)]); } return result.join(''); }; exports.has = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }; /** * generate a real map object, no constructor, no __proto__ * @param {Object} [obj], init object, optional * @return {Object} */ exports.map = function (obj) { var map = Object.create(null); if (!obj) { return map; } for (var key in obj) { map[key] = obj[key]; } return map; };