'use strict'; // @ts-check // ================================================================================== // utils.js // ---------------------------------------------------------------------------------- // Description: System Information - library // for Node.js // Copyright: (c) 2014 - 2020 // Author: Sebastian Hildebrandt // ---------------------------------------------------------------------------------- // License: MIT // ================================================================================== // 0. helper functions // ---------------------------------------------------------------------------------- const os = require('os'); const fs = require('fs'); const spawn = require('child_process').spawn; const exec = require('child_process').exec; const execSync = require('child_process').execSync; const util = require('util'); let _platform = process.platform; const _linux = (_platform === 'linux'); const _darwin = (_platform === 'darwin'); const _windows = (_platform === 'win32'); const _freebsd = (_platform === 'freebsd'); const _openbsd = (_platform === 'openbsd'); const _netbsd = (_platform === 'netbsd'); // const _sunos = (_platform === 'sunos'); let _cores = 0; let wmicPath = ''; let codepage = ''; const WINDIR = process.env.WINDIR || 'C:\\Windows'; const execOptsWin = { windowsHide: true, maxBuffer: 1024 * 20000, encoding: 'UTF-8', env: util._extend({}, process.env, { LANG: 'en_US.UTF-8' }) }; function toInt(value) { let result = parseInt(value, 10); if (isNaN(result)) { result = 0; } return result; } const stringReplace = new String().replace; const stringToLower = new String().toLowerCase; const stringToString = new String().toString; const stringSubstr = new String().substr; const stringTrim = new String().trim; function isFunction(functionToCheck) { let getType = {}; return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; } function unique(obj) { let uniques = []; let stringify = {}; for (let i = 0; i < obj.length; i++) { let keys = Object.keys(obj[i]); keys.sort(function (a, b) { return a - b; }); let str = ''; for (let j = 0; j < keys.length; j++) { str += JSON.stringify(keys[j]); str += JSON.stringify(obj[i][keys[j]]); } if (!{}.hasOwnProperty.call(stringify, str)) { uniques.push(obj[i]); stringify[str] = true; } } return uniques; } function sortByKey(array, keys) { return array.sort(function (a, b) { let x = ''; let y = ''; keys.forEach(function (key) { x = x + a[key]; y = y + b[key]; }); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }); } function cores() { if (_cores === 0) { _cores = os.cpus().length; } return _cores; } function getValue(lines, property, separator, trimmed) { separator = separator || ':'; property = property.toLowerCase(); trimmed = trimmed || false; for (let i = 0; i < lines.length; i++) { let line = lines[i].toLowerCase().replace(/\t/g, ''); if (trimmed) { line = line.trim(); } if (line.startsWith(property)) { const parts = lines[i].split(separator); if (parts.length >= 2) { parts.shift(); return parts.join(separator).trim(); } else { return ''; } } } return ''; } function decodeEscapeSequence(str, base) { base = base || 16; return str.replace(/\\x([0-9A-Fa-f]{2})/g, function () { return String.fromCharCode(parseInt(arguments[1], base)); }); } function detectSplit(str) { let seperator = ''; let part = 0; str.split('').forEach(element => { if (element >= '0' && element <= '9') { if (part === 1) { part++; } } else { if (part === 0) { part++; } if (part === 1) { seperator += element; } } }); return seperator; } function parseTime(t, pmDesignator) { pmDesignator = pmDesignator || ''; t = t.toUpperCase(); let hour = 0; let min = 0; let splitter = detectSplit(t); let parts = t.split(splitter); if (parts.length >= 2) { if (parts[2]) { parts[1] += parts[2]; } let isPM = (parts[1] && (parts[1].toLowerCase().indexOf('pm') > -1) || (parts[1].toLowerCase().indexOf('p.m.') > -1) || (parts[1].toLowerCase().indexOf('p. m.') > -1) || (parts[1].toLowerCase().indexOf('n') > -1) || (parts[1].toLowerCase().indexOf('ch') > -1) || (parts[1].toLowerCase().indexOf('ös') > -1) || (pmDesignator && parts[1].toLowerCase().indexOf(pmDesignator) > -1)); hour = parseInt(parts[0], 10); min = parseInt(parts[1], 10); hour = isPM && hour < 12 ? hour + 12 : hour; return ('0' + hour).substr(-2) + ':' + ('0' + min).substr(-2); } } function parseDateTime(dt, culture) { const result = { date: '', time: '' }; culture = culture || {}; let dateFormat = (culture.dateFormat || '').toLowerCase(); let pmDesignator = (culture.pmDesignator || ''); const parts = dt.split(' '); if (parts[0]) { if (parts[0].indexOf('/') >= 0) { // Dateformat: mm/dd/yyyy or dd/mm/yyyy or dd/mm/yy or yyyy/mm/dd const dtparts = parts[0].split('/'); if (dtparts.length === 3) { if (dtparts[0].length === 4) { // Dateformat: yyyy/mm/dd result.date = dtparts[0] + '-' + ('0' + dtparts[1]).substr(-2) + '-' + ('0' + dtparts[2]).substr(-2); } else if (dtparts[2].length === 2) { if ((dateFormat.indexOf('/d/') > -1 || dateFormat.indexOf('/dd/') > -1)) { // Dateformat: mm/dd/yy result.date = '20' + dtparts[2] + '-' + ('0' + dtparts[1]).substr(-2) + '-' + ('0' + dtparts[0]).substr(-2); } else { // Dateformat: dd/mm/yy result.date = '20' + dtparts[2] + '-' + ('0' + dtparts[1]).substr(-2) + '-' + ('0' + dtparts[0]).substr(-2); } } else { // Dateformat: mm/dd/yyyy or dd/mm/yyyy const isEN = ((dt.toLowerCase().indexOf('pm') > -1) || (dt.toLowerCase().indexOf('p.m.') > -1) || (dt.toLowerCase().indexOf('p. m.') > -1) || (dt.toLowerCase().indexOf('am') > -1) || (dt.toLowerCase().indexOf('a.m.') > -1) || (dt.toLowerCase().indexOf('a. m.') > -1)); if ((isEN || dateFormat.indexOf('/d/') > -1 || dateFormat.indexOf('/dd/') > -1) && dateFormat.indexOf('dd/') !== 0) { // Dateformat: mm/dd/yyyy result.date = dtparts[2] + '-' + ('0' + dtparts[0]).substr(-2) + '-' + ('0' + dtparts[1]).substr(-2); } else { // Dateformat: dd/mm/yyyy result.date = dtparts[2] + '-' + ('0' + dtparts[1]).substr(-2) + '-' + ('0' + dtparts[0]).substr(-2); } } } } if (parts[0].indexOf('.') >= 0) { const dtparts = parts[0].split('.'); if (dtparts.length === 3) { if (dateFormat.indexOf('.d.') > -1 || dateFormat.indexOf('.dd.') > -1) { // Dateformat: mm.dd.yyyy result.date = dtparts[2] + '-' + ('0' + dtparts[0]).substr(-2) + '-' + ('0' + dtparts[1]).substr(-2); } else { // Dateformat: dd.mm.yyyy result.date = dtparts[2] + '-' + ('0' + dtparts[1]).substr(-2) + '-' + ('0' + dtparts[0]).substr(-2); } } } if (parts[0].indexOf('-') >= 0) { // Dateformat: yyyy-mm-dd const dtparts = parts[0].split('-'); if (dtparts.length === 3) { result.date = dtparts[0] + '-' + ('0' + dtparts[1]).substr(-2) + '-' + ('0' + dtparts[2]).substr(-2); } } } if (parts[1]) { parts.shift(); let time = parts.join(' '); result.time = parseTime(time, pmDesignator); } return result; } function parseHead(head, rights) { let space = (rights > 0); let count = 1; let from = 0; let to = 0; let result = []; for (let i = 0; i < head.length; i++) { if (count <= rights) { // if (head[i] === ' ' && !space) { if (/\s/.test(head[i]) && !space) { to = i - 1; result.push({ from: from, to: to + 1, cap: head.substring(from, to + 1) }); from = to + 2; count++; } space = head[i] === ' '; } else { if (!/\s/.test(head[i]) && space) { to = i - 1; if (from < to) { result.push({ from: from, to: to, cap: head.substring(from, to) }); } from = to + 1; count++; } space = head[i] === ' '; } } to = 1000; result.push({ from: from, to: to, cap: head.substring(from, to) }); let len = result.length; for (var i = 0; i < len; i++) { if (result[i].cap.replace(/\s/g, '').length === 0) { if (i + 1 < len) { result[i].to = result[i + 1].to; result[i].cap = result[i].cap + result[i + 1].cap; result.splice(i + 1, 1); len = len - 1; } } } return result; } function findObjectByKey(array, key, value) { for (let i = 0; i < array.length; i++) { if (array[i][key] === value) { return i; } } return -1; } function getWmic() { if (os.type() === 'Windows_NT' && !wmicPath) { wmicPath = WINDIR + '\\system32\\wbem\\wmic.exe'; if (!fs.existsSync(wmicPath)) { try { const wmicPathArray = execSync('WHERE WMIC').toString().split('\r\n'); if (wmicPathArray && wmicPathArray.length) { wmicPath = wmicPathArray[0]; } else { wmicPath = 'wmic'; } } catch (e) { wmicPath = 'wmic'; } } } return wmicPath; } function wmic(command, options) { options = options || execOptsWin; return new Promise((resolve) => { process.nextTick(() => { try { exec(WINDIR + '\\system32\\chcp.com 65001 | ' + getWmic() + ' ' + command, options, function (error, stdout) { resolve(stdout, error); }).stdin.end(); } catch (e) { resolve('', e); } }); }); } function getVboxmanage() { return _windows ? process.env.VBOX_INSTALL_PATH || process.env.VBOX_MSI_INSTALL_PATH + '\\VBoxManage.exe' + '" ' : 'vboxmanage'; } function powerShell(cmd) { let result = ''; return new Promise((resolve) => { process.nextTick(() => { try { const child = spawn('powershell.exe', ['-NoLogo', '-InputFormat', 'Text', '-NoExit', '-ExecutionPolicy', 'Unrestricted', '-Command', '-'], { stdio: 'pipe', windowsHide: true, maxBuffer: 1024 * 20000, encoding: 'UTF-8', env: util._extend({}, process.env, { LANG: 'en_US.UTF-8' }) }); if (child && !child.pid) { child.on('error', function () { resolve(result); }); } if (child && child.pid) { child.stdout.on('data', function (data) { result = result + data.toString('utf8'); }); child.stderr.on('data', function () { child.kill(); resolve(result); }); child.on('close', function () { child.kill(); resolve(result); }); child.on('error', function () { child.kill(); resolve(result); }); try { child.stdin.write(cmd + os.EOL); child.stdin.write('exit' + os.EOL); child.stdin.end(); } catch (e) { child.kill(); resolve(result); } } else { resolve(result); } } catch (e) { resolve(result); } }); }); } function getCodepage() { if (_windows) { if (!codepage) { try { const stdout = execSync('chcp'); const lines = stdout.toString().split('\r\n'); const parts = lines[0].split(':'); codepage = parts.length > 1 ? parts[1].replace('.', '') : ''; } catch (err) { codepage = '437'; } } return codepage; } if (_linux || _darwin || _freebsd || _openbsd || _netbsd) { if (!codepage) { try { const stdout = execSync('echo $LANG'); const lines = stdout.toString().split('\r\n'); const parts = lines[0].split('.'); codepage = parts.length > 1 ? parts[1].trim() : ''; if (!codepage) { codepage = 'UTF-8'; } } catch (err) { codepage = 'UTF-8'; } } return codepage; } } function isRaspberry() { const PI_MODEL_NO = [ 'BCM2708', 'BCM2709', 'BCM2710', 'BCM2835', 'BCM2837B0' ]; let cpuinfo = []; try { cpuinfo = fs.readFileSync('/proc/cpuinfo', { encoding: 'utf8' }).split('\n'); } catch (e) { return false; } const hardware = getValue(cpuinfo, 'hardware'); return (hardware && PI_MODEL_NO.indexOf(hardware) > -1); } function isRaspbian() { let osrelease = []; try { osrelease = fs.readFileSync('/etc/os-release', { encoding: 'utf8' }).split('\n'); } catch (e) { return false; } const id = getValue(osrelease, 'id'); return (id && id.indexOf('raspbian') > -1); } function execWin(cmd, opts, callback) { if (!callback) { callback = opts; opts = execOptsWin; } let newCmd = 'chcp 65001 > nul && cmd /C ' + cmd + ' && chcp ' + codepage + ' > nul'; exec(newCmd, opts, function (error, stdout) { callback(error, stdout); }); } function darwinXcodeExists() { const cmdLineToolsExists = fs.existsSync('/Library/Developer/CommandLineTools/usr/bin/'); const xcodeAppExists = fs.existsSync('/Applications/Xcode.app/Contents/Developer/Tools'); const xcodeExists = fs.existsSync('/Library/Developer/Xcode/'); return (cmdLineToolsExists || xcodeExists || xcodeAppExists); } function nanoSeconds() { const time = process.hrtime(); if (!Array.isArray(time) || time.length !== 2) { return 0; } return +time[0] * 1e9 + +time[1]; } function countUniqueLines(lines, startingWith) { startingWith = startingWith || ''; const uniqueLines = []; lines.forEach(line => { if (line.startsWith(startingWith)) { if (uniqueLines.indexOf(line) === -1) { uniqueLines.push(line); } } }); return uniqueLines.length; } function countLines(lines, startingWith) { startingWith = startingWith || ''; const uniqueLines = []; lines.forEach(line => { if (line.startsWith(startingWith)) { uniqueLines.push(line); } }); return uniqueLines.length; } function sanitizeShellString(str) { const s = str || ''; let result = ''; for (let i = 0; i <= 2000; i++) { if (!(s[i] === undefined || s[i] === '>' || s[i] === '<' || s[i] === '*' || s[i] === '?' || s[i] === '[' || s[i] === ']' || s[i] === '|' || s[i] === '˚' || s[i] === '$' || s[i] === ';' || s[i] === '&' || s[i] === '(' || s[i] === ')' || s[i] === ']' || s[i] === '#' || s[i] === '\\' || s[i] === '\t' || s[i] === '\n' || s[i] === '"')) { result = result + s[i]; } } return result; } function isPrototypePolluted() { const s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' let notPolluted = true; let st = ''; st.__proto__.replace = stringReplace; st.__proto__.toLowerCase = stringToLower; st.__proto__.toString = stringToString; st.__proto__.substr = stringSubstr; notPolluted = notPolluted || !(s.length === 62) const ms = Date.now(); if (typeof ms === 'number' && ms > 1600000000000) { const l = ms % 100 + 15; for (let i = 0; i < l; i++) { const r = Math.random() * 61.99999999 + 1; const rs = parseInt(Math.floor(r).toString(), 10) const rs2 = parseInt(r.toString().split('.')[0], 10); const q = Math.random() * 61.99999999 + 1; const qs = parseInt(Math.floor(q).toString(), 10) const qs2 = parseInt(q.toString().split('.')[0], 10); notPolluted = notPolluted && !(r === q); notPolluted = notPolluted && rs === rs2 && qs === qs2; st += s[rs - 1]; } notPolluted = notPolluted && st.length === l; // string manipulation let p = Math.random() * l * 0.9999999999; let stm = st.substr(0, p) + ' ' + st.substr(p, 2000); stm.__proto__.replace = stringReplace; let sto = stm.replace(/ /g, ''); notPolluted = notPolluted && st === sto; p = Math.random() * l * 0.9999999999; stm = st.substr(0, p) + '{' + st.substr(p, 2000); sto = stm.replace(/{/g, ''); notPolluted = notPolluted && st === sto; p = Math.random() * l * 0.9999999999; stm = st.substr(0, p) + '*' + st.substr(p, 2000); sto = stm.replace(/\*/g, ''); notPolluted = notPolluted && st === sto; p = Math.random() * l * 0.9999999999; stm = st.substr(0, p) + '$' + st.substr(p, 2000); sto = stm.replace(/\$/g, ''); notPolluted = notPolluted && st === sto; // lower const stl = st.toLowerCase(); notPolluted = notPolluted && (stl.length === l) && stl[l - 1] && !(stl[l]) for (let i = 0; i < l; i++) { const s1 = st[i]; s1.__proto__.toLowerCase = stringToLower; const s2 = stl ? stl[i] : ''; const s1l = s1.toLowerCase(); notPolluted = notPolluted && s1l[0] === s2 && s1l[0] && !(s1l[1]); } } return !notPolluted; } function hex2bin(hex) { return ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8); } function decodePiCpuinfo(lines) { // https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md const oldRevisionCodes = { '0002': { type: 'B', revision: '1.0', memory: 256, manufacturer: 'Egoman', processor: 'BCM2835' }, '0003': { type: 'B', revision: '1.0', memory: 256, manufacturer: 'Egoman', processor: 'BCM2835' }, '0004': { type: 'B', revision: '2.0', memory: 256, manufacturer: 'Sony UK', processor: 'BCM2835' }, '0005': { type: 'B', revision: '2.0', memory: 256, manufacturer: 'Qisda', processor: 'BCM2835' }, '0006': { type: 'B', revision: '2.0', memory: 256, manufacturer: 'Egoman', processor: 'BCM2835' }, '0007': { type: 'A', revision: '2.0', memory: 256, manufacturer: 'Egoman', processor: 'BCM2835' }, '0008': { type: 'A', revision: '2.0', memory: 256, manufacturer: 'Sony UK', processor: 'BCM2835' }, '0009': { type: 'A', revision: '2.0', memory: 256, manufacturer: 'Qisda', processor: 'BCM2835' }, '000d': { type: 'B', revision: '2.0', memory: 512, manufacturer: 'Egoman', processor: 'BCM2835' }, '000e': { type: 'B', revision: '2.0', memory: 512, manufacturer: 'Sony UK', processor: 'BCM2835' }, '000f': { type: 'B', revision: '2.0', memory: 512, manufacturer: 'Egoman', processor: 'BCM2835' }, '0010': { type: 'B+', revision: '1.2', memory: 512, manufacturer: 'Sony UK', processor: 'BCM2835' }, '0011': { type: 'CM1', revision: '1.0', memory: 512, manufacturer: 'Sony UK', processor: 'BCM2835' }, '0012': { type: 'A+', revision: '1.1', memory: 256, manufacturer: 'Sony UK', processor: 'BCM2835' }, '0013': { type: 'B+', revision: '1.2', memory: 512, manufacturer: 'Embest', processor: 'BCM2835' }, '0014': { type: 'CM1', revision: '1.0', memory: 512, manufacturer: 'Embest', processor: 'BCM2835' }, '0015': { type: 'A+', revision: '1.1', memory: 256, manufacturer: '512MB Embest', processor: 'BCM2835' } } const processorList = [ 'BCM2835', 'BCM2836', 'BCM2837', 'BCM2711', ]; const manufacturerList = [ 'Sony UK', 'Egoman', 'Embest', 'Sony Japan', 'Embest', 'Stadium' ]; const typeList = { '00': 'A', '01': 'B', '02': 'A+', '03': 'B+', '04': '2B', '05': 'Alpha (early prototype)', '06': 'CM1', '08': '3B', '09': 'Zero', '0a': 'CM3', '0c': 'Zero W', '0d': '3B+', '0e': '3A+', '0f': 'Internal use only', '10': 'CM3+', '11': '4B', '13': '400', '14': 'CM4' }; const revisionCode = getValue(lines, 'revision', ':', true); const model = getValue(lines, 'model:', ':', true); const serial = getValue(lines, 'serial', ':', true); let result = {}; if (oldRevisionCodes.hasOwnProperty(revisionCode)) { // old revision codes result = { model, serial, revisionCode, memory: oldRevisionCodes[revisionCode].memory, manufacturer: oldRevisionCodes[revisionCode].manufacturer, processor: oldRevisionCodes[revisionCode].processor, type: oldRevisionCodes[revisionCode].type, revision: oldRevisionCodes[revisionCode].revision, } } else { // new revision code const revision = ('00000000' + getValue(lines, 'revision', ':', true).toLowerCase()).substr(-8); // const revisionStyleNew = hex2bin(revision.substr(2, 1)).substr(4, 1) === '1'; const memSizeCode = parseInt(hex2bin(revision.substr(2, 1)).substr(5, 3), 2) || 0; const manufacturer = manufacturerList[parseInt(revision.substr(3, 1), 10)]; const processor = processorList[parseInt(revision.substr(4, 1), 10)]; const typeCode = revision.substr(5, 2); result = { model, serial, revisionCode, memory: 256 * Math.pow(2, memSizeCode), manufacturer, processor, type: typeList.hasOwnProperty(typeCode) ? typeList[typeCode] : '', revision: '1.' + revision.substr(7, 1), } } return result; } function noop() { } exports.toInt = toInt; exports.execOptsWin = execOptsWin; exports.getCodepage = getCodepage; exports.execWin = execWin; exports.isFunction = isFunction; exports.unique = unique; exports.sortByKey = sortByKey; exports.cores = cores; exports.getValue = getValue; exports.decodeEscapeSequence = decodeEscapeSequence; exports.parseDateTime = parseDateTime; exports.parseHead = parseHead; exports.findObjectByKey = findObjectByKey; exports.getWmic = getWmic; exports.wmic = wmic; exports.darwinXcodeExists = darwinXcodeExists; exports.getVboxmanage = getVboxmanage; exports.powerShell = powerShell; exports.nanoSeconds = nanoSeconds; exports.countUniqueLines = countUniqueLines; exports.countLines = countLines; exports.noop = noop; exports.isRaspberry = isRaspberry; exports.isRaspbian = isRaspbian; exports.sanitizeShellString = sanitizeShellString; exports.isPrototypePolluted = isPrototypePolluted; exports.decodePiCpuinfo = decodePiCpuinfo; exports.stringReplace = stringReplace; exports.stringToLower = stringToLower; exports.stringToString = stringToString; exports.stringSubstr = stringSubstr; exports.stringTrim = stringTrim;