const fs = require('fs'); const path = require('path'); const os = require('os'); const axios = require('axios/index'); const exec = require('child_process').exec; const execFile = require('child_process').execFile; const spawn = require('child_process').spawn; const Constants = require('./constants'); const RandomString = require('randomstring'); const tryParse = (j, def) => { try { return JSON.parse(j); } catch (e) { return def; } }; module.exports.createSignatureFiles = (signature, publicKey) => { const fileName1 = RandomString.generate({ length: 12, charset: 'alphabetic' }); const fileName2 = RandomString.generate({ length: 12, charset: 'alphabetic' }); const signatureFile = path.join(os.tmpdir(), fileName1 + '.sig'); const publicKeyFile = path.join(os.tmpdir(), fileName2 + '.pub'); const buffer = new Buffer(signature, 'base64'); fs.writeFileSync(signatureFile, buffer); fs.writeFileSync(publicKeyFile, publicKey); return { PublicKeyFile: publicKeyFile, SignatureFile: signatureFile, }; }; module.exports.detectRepertoryMounts = (directory, version) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-status'); const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { let defaultData = {}; for (const provider of Constants.PROVIDER_LIST) { defaultData[provider] = { Active: false, Location: '', PID: -1, }; } resolve(tryParse(result, defaultData)); }); process.unref(); }); }; module.exports.downloadFile = (url, destination, progressCallback, completeCallback) => { try { if (fs.existsSync(destination)) { fs.unlinkSync(destination); } } catch (e) { completeCallback(false, e); return; } axios .get(url, { responseType: 'stream', }) .then((response) => { const stream = fs.createWriteStream(destination); const total = parseInt(response.headers['content-length'], 10); let downloaded = 0; response.data.on('data', (chunk) => { stream.write(Buffer.from(chunk)); downloaded += chunk.length; progressCallback((downloaded / total * 100.0).toFixed(2)); }); response.data.on('end', () => { stream.end(() => { completeCallback(); }); }); response.data.on('error', (e) => { stream.end(() => { completeCallback(e); }); }); }) .catch((e)=> { completeCallback(e); }); }; module.exports.executeAndWait = (command, ignoreResult) => { return new Promise((resolve, reject) => { const retryExecute = (count, lastError) => { if (++count <= 5) { exec(command, error => { if (error) { if (!ignoreResult && (error.code === 1)) { setTimeout(() => { retryExecute(count, error); }, 1000); } else { reject(error); } } else { resolve(); } }); } else { reject(lastError); } }; retryExecute(0); }); }; module.exports.executeAsync = (command, args=[]) => { return new Promise((resolve, reject) => { const launchProcess = (count, timeout) => { const processOptions = { detached: true, shell: false, }; const process = new spawn(command, args, processOptions); const pid = process.pid; process.on('error', (err) => { if (++count === 5) { reject(err, pid); } else { clearTimeout(timeout); setTimeout(()=> launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); } }); process.on('exit', (code) => { if (code !== 0) { if (++count === 5) { reject(code, pid); } else { clearTimeout(timeout); setTimeout(() => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); } } }); process.unref(); }; launchProcess(0, setTimeout(() => resolve(), 3000)); }); }; module.exports.executeScript = script => { return new Promise((resolve, reject) => { const processOptions = { detached: false, shell: true, windowsHide: true, }; const command = '/bin/sh'; const args = [ script ]; const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { resolve(result); }); process.unref(); }); }; module.exports.executeMount = (directory, version, storageType, location, noConsoleSupported, exitCallback) => { return new Promise((resolve) => { const processOptions = { detached: false, shell: os.platform() !== 'darwin', stdio: 'ignore', }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) { args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]); } if ((os.platform() === 'linux') || (os.platform() === 'darwin')) { args.push('-o'); args.push('big_writes'); args.push('-f'); if (noConsoleSupported) { args.push('-nc'); } } else if (os.platform() === 'win32') { args.push('-hidden'); } args.push(location); let process = new spawn(command, args, processOptions); const pid = process.pid; const timeout = setTimeout(() => { resolve(pid); }, 3000); process.on('error', (err) => { clearTimeout(timeout); exitCallback(err, pid); }); process.on('exit', (code) => { clearTimeout(timeout); exitCallback(code, pid); }); }); }; module.exports.getConfig = (directory, version, storageType) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-dc'); if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) { args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]); } const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { const lines = result .replace(/\r\n/g, '\n') .split('\n'); const code = parseInt(lines[0], 10); if (code === 0) { lines.shift(); resolve({ Code: code, Data: JSON.parse(lines.join('\n')), }); } else { resolve(code); } }); process.unref(); }); }; module.exports.getConfigTemplate = (directory, version, storageType) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-gt'); if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) { args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]); } const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d)=> { result += d; }); process.on('exit', () => { resolve(JSON.parse(result)); }); process.unref(); }); }; module.exports.getMissingDependencies = dependencies => { return new Promise((resolve, reject) => { if (!dependencies) { reject(Error('Dependency list is invalid')); } let missing = []; if (os.platform() === 'win32') { let count = 0; const resolveIfComplete = () => { if (++count === dependencies.length) { resolve(missing); } }; const Registry = require('winreg'); for (const dep of dependencies) { let hive = null; const hiveName = dep.registry[0].split('\\')[0]; switch (hiveName) { case 'HKEY_CLASSES_ROOT': hive = Registry.HKCR; break; case 'HKEY_CURRENT_CONFIG': hive = Registry.HKCC; break; case 'HKEY_CURRENT_USER': hive = Registry.HKCU; break; case 'HKEY_LOCAL_MACHINE': hive = Registry.HKLM; break; case 'HKEY_USERS': hive = Registry.HKU; break; default: throw Error('Invalid registry hive: ' + hiveName); } const key = dep.registry[0].substr(hiveName.length); const regKey = new Registry({ hive: hive, key: key }); regKey.valueExists('DisplayName', (err, exists) => { if (err || !exists) { regKey.valueExists('ProductName', (err, exists) => { if (err || !exists) { missing.push(dep); } resolveIfComplete(); }); } else { resolveIfComplete(); } }); } } else { for (const dep of dependencies) { try { if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) { missing.push(dep); } } catch (e) { missing.push(dep); } } resolve(missing); } }); }; //https://stackoverflow.com/questions/31645738/how-to-create-full-path-with-nodes-fs-mkdirsync module.exports.mkDirByPathSync = (targetDir, { isRelativeToScript = false } = {}) => { const sep = path.sep; const initDir = path.isAbsolute(targetDir) ? sep : ''; const baseDir = isRelativeToScript ? __dirname : '.'; return targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = path.resolve(baseDir, parentDir, childDir); try { fs.mkdirSync(curDir); } catch (err) { if (err.code === 'EEXIST') { // curDir already exists! return curDir; } // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); } const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; if (!caughtErr || (caughtErr && (targetDir === curDir))) { throw err; // Throw if it's just the last created dir. } } return curDir; }, initDir); }; module.exports.removeDirectoryRecursively = (p) => { if (fs.existsSync(p)) { fs .readdirSync(p) .forEach(file => { const curPath = path.join(p, file); if (fs.lstatSync(curPath).isDirectory()) { module.exports.removeDirectoryRecursively(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(p); } }; module.exports.resolvePath = str => { if (os.platform() === 'win32') { return str.replace(/%([^%]+)%/g, (_, n) => { return process.env[n]; }); } else { return str.replace('~', os.homedir()); } }; module.exports.setConfigValue = (name, value, directory, storageType, version) => { return new Promise((resolve, reject) => { const processOptions = { detached: true, shell: false, windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = []; args.push('-set'); args.push(name); args.push(value); if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) { args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]); } const process = new spawn(command, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', () => { resolve(); }); process.unref(); }); }; module.exports.stopMountProcess = (directory, version, storageType) => { return new Promise((resolve, reject) => { const processOptions = { detached: os.platform() === 'darwin', shell: os.platform() !== 'darwin', windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = ['-unmount']; if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) { args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]); } const process = new spawn(command, args, processOptions); const pid = process.pid; process.on('error', (err) => { reject(err); }); process.on('exit', (code) => { resolve({ PID: pid, Code: code, }); }); if (os.platform() === 'darwin') { process.unref(); } }); }; module.exports.stopMountProcessSync = (directory, version, storageType) => { const processOptions = { detached: true, shell: os.platform() !== 'darwin', windowsHide: true, }; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const args = ['-unmount']; if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) { args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]); } const process = new spawn(command, args, processOptions); process.unref(); }; module.exports.verifySignature = (file, signatureFile, publicKeyFile) => { return new Promise((resolve, reject) => { const executeVerify = openssl => { //openssl dgst -sha256 -verify $pubkeyfile -signature signature.sig file execFile(openssl, ['dgst', '-sha256', '-verify', publicKeyFile, '-signature', signatureFile], res => { if (res.code !== 0) { reject(res); } else { resolve(); } }); }; if (os.platform() === 'win32') { const Registry = require('winreg'); const regKey = new Registry({ hive: Registry.HKLM, key: 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1' }); regKey.valueExists('InstallLocation', (err, exists) => { if (err) { reject(err); } else if (exists) { regKey.get('InstallLocation', (err, item) => { if (err) { reject(err); } else { executeVerify(path.join(item.value(), 'bin', 'openssl.exe')); } }); } else { reject('Failed to locate \'openssl.exe\''); } }); } else if (os.platform() === 'linux') { executeVerify('openssl'); } else { reject('Platform not supported: ' + os.platform()) } }); }; module.exports.verifyHash = (file, hash) => { return new Promise((resolve, reject) => { const platform = os.platform(); let command; let args; if (platform === 'darwin') { command = 'shasum'; args = ['-b', '-a', '256', file]; } else if (platform === 'linux') { command = 'sha256sum'; args = ['-b', file, '-z']; } else { reject('Platform not supported: ' + os.platform()) } if (command) { execFile(command, args, (err, stdout) => { if (err) { reject(err); } else { const hash2 = stdout.split(' ')[0].trim().toLowerCase(); if (hash2 === hash.toLowerCase()) { resolve(); } else { reject('Checksum failed for file'); } } }); } }); };