mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-18 16:56:32 +00:00
Fix elevation on windows when the path contains "&" or "'"
Change-type: patch Changelog-entry: Fix elevation on windows when the path contains "&" or "'"
This commit is contained in:
parent
d9b340ca45
commit
6d8346b13a
62
lib/gui/app/os/windows-network-drives.js
Normal file → Executable file
62
lib/gui/app/os/windows-network-drives.js
Normal file → Executable file
@ -23,59 +23,9 @@ const _ = require('lodash')
|
|||||||
const os = require('os')
|
const os = require('os')
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const process = require('process')
|
const process = require('process')
|
||||||
const tmp = require('tmp')
|
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
|
|
||||||
/**
|
const { tmpFileDisposer } = require('../../../shared/utils')
|
||||||
* @summary returns { path: String, cleanup: Function }
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @returns {Promise<{ path: String, cleanup: Function }>}
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* tmpFileAsync()
|
|
||||||
* .then({ path, cleanup } => {
|
|
||||||
* console.log(path)
|
|
||||||
* cleanup()
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const tmpFileAsync = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const options = {
|
|
||||||
|
|
||||||
// Close the file once it's created
|
|
||||||
discardDescriptor: true,
|
|
||||||
|
|
||||||
// Wmic fails with "Invalid global switch" when the "/output:" switch filename contains a dash ("-")
|
|
||||||
prefix: 'tmp'
|
|
||||||
}
|
|
||||||
tmp.file(options, (error, path, _fd, cleanup) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
resolve({ path, cleanup })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Disposer for tmpFileAsync, calls cleanup()
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @returns {Disposer<{ path: String, cleanup: Function }>}
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await Bluebird.using(tmpFileDisposer(), ({ path }) => {
|
|
||||||
* console.log(path);
|
|
||||||
* })
|
|
||||||
*/
|
|
||||||
const tmpFileDisposer = () => {
|
|
||||||
return Bluebird.resolve(tmpFileAsync())
|
|
||||||
.disposer(({ cleanup }) => {
|
|
||||||
cleanup()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const readFileAsync = promisify(fs.readFile)
|
const readFileAsync = promisify(fs.readFile)
|
||||||
|
|
||||||
@ -99,7 +49,15 @@ exports.getWmicNetworkDrivesOutput = async () => {
|
|||||||
// We could also use wmic's "/output:" switch but it doesn't work when the filename
|
// We could also use wmic's "/output:" switch but it doesn't work when the filename
|
||||||
// contains a space and the os temp dir may contain spaces ("D:\Windows Temp Files" for example).
|
// contains a space and the os temp dir may contain spaces ("D:\Windows Temp Files" for example).
|
||||||
// So we just redirect to a file and read it afterwards as we know it will be ucs2 encoded.
|
// So we just redirect to a file and read it afterwards as we know it will be ucs2 encoded.
|
||||||
return Bluebird.using(tmpFileDisposer(), async ({ path }) => {
|
const options = {
|
||||||
|
|
||||||
|
// Close the file once it's created
|
||||||
|
discardDescriptor: true,
|
||||||
|
|
||||||
|
// Wmic fails with "Invalid global switch" when the "/output:" switch filename contains a dash ("-")
|
||||||
|
prefix: 'tmp'
|
||||||
|
}
|
||||||
|
return Bluebird.using(tmpFileDisposer(options), async ({ path }) => {
|
||||||
const command = [
|
const command = [
|
||||||
Path.join(process.env.SystemRoot, 'System32', 'Wbem', 'wmic'),
|
Path.join(process.env.SystemRoot, 'System32', 'Wbem', 'wmic'),
|
||||||
'path',
|
'path',
|
||||||
|
226
lib/shared/permissions.js
Normal file → Executable file
226
lib/shared/permissions.js
Normal file → Executable file
@ -14,17 +14,24 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable lodash/prefer-lodash-method,quotes,no-magic-numbers,require-jsdoc */
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const os = require('os')
|
|
||||||
const nativeModule = require('./native-module')
|
|
||||||
const Bluebird = require('bluebird')
|
const Bluebird = require('bluebird')
|
||||||
const childProcess = Bluebird.promisifyAll(require('child_process'))
|
const childProcess = Bluebird.promisifyAll(require('child_process'))
|
||||||
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'))
|
const fs = require('fs')
|
||||||
const commandJoin = require('command-join')
|
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
const os = require('os')
|
||||||
|
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'))
|
||||||
|
const { promisify } = require('util')
|
||||||
|
|
||||||
const errors = require('./errors')
|
const errors = require('./errors')
|
||||||
|
|
||||||
|
const { tmpFileDisposer } = require('./utils')
|
||||||
|
|
||||||
|
const writeFileAsync = promisify(fs.writeFile)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary The user id of the UNIX "superuser"
|
* @summary The user id of the UNIX "superuser"
|
||||||
* @constant
|
* @constant
|
||||||
@ -90,68 +97,61 @@ exports.isElevatedUnixSync = () => {
|
|||||||
return (process.geteuid() === UNIX_SUPERUSER_USER_ID)
|
return (process.geteuid() === UNIX_SUPERUSER_USER_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const escapeSh = (value) => {
|
||||||
* @summary Get environment command prefix
|
// Make sure it's a string
|
||||||
* @function
|
// Replace ' -> '\'' (closing quote, escaped quote, opening quote)
|
||||||
* @private
|
// Surround with quotes
|
||||||
*
|
return `'${String(value).replace(/'/g, "'\\''")}'`
|
||||||
* @param {Object} environment - environment map
|
|
||||||
* @returns {String[]} command arguments
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const commandPrefix = permissions.getEnvironmentCommandPrefix({
|
|
||||||
* FOO: 'bar',
|
|
||||||
* BAR: 'baz'
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* childProcess.execSync(_.join(_.concat(commandPrefix, [ 'mycommand' ]), ' '));
|
|
||||||
*/
|
|
||||||
exports.getEnvironmentCommandPrefix = (environment) => {
|
|
||||||
const isWindows = os.platform() === 'win32'
|
|
||||||
|
|
||||||
if (_.isEmpty(environment)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const argv = _.flatMap(environment, (value, key) => {
|
|
||||||
if (_.isNil(value)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isWindows) {
|
|
||||||
// Doing something like `set foo=bar &&` (notice
|
|
||||||
// the space before the first ampersand) would
|
|
||||||
// cause the environment variable's value to
|
|
||||||
// contain a trailing space.
|
|
||||||
// See https://superuser.com/a/57726
|
|
||||||
return [ 'set', `${key}=${value}&&` ]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ `${key}=${value}` ]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isWindows) {
|
|
||||||
// This is a trick to make the binary afterwards catch
|
|
||||||
// the environment variables set just previously.
|
|
||||||
return _.concat(argv, [ 'call' ])
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.concat([ 'env' ], argv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const escapeParamCmd = (value) => {
|
||||||
* @summary Quote a string
|
// Make sure it's a string
|
||||||
* @function
|
// Escape " -> \"
|
||||||
* @private
|
// Surround with double quotes
|
||||||
*
|
return `"${String(value).replace(/"/g, '\\"')}"`
|
||||||
* @param {String} string - input string
|
}
|
||||||
* @returns {String} quoted string
|
|
||||||
*
|
const setEnvVarSh = (value, name) => {
|
||||||
* @example
|
return `export ${name}=${escapeSh(value)}`
|
||||||
* const result = quote('foo');
|
}
|
||||||
*/
|
|
||||||
const quoteString = (string) => {
|
const setEnvVarCmd = (value, name) => {
|
||||||
return `"${string}"`
|
return `set "${name}=${String(value)}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported for tests
|
||||||
|
exports.createLaunchScript = (command, argv, environment) => {
|
||||||
|
const isWindows = os.platform() === 'win32'
|
||||||
|
const lines = []
|
||||||
|
if (isWindows) {
|
||||||
|
// Switch to utf8
|
||||||
|
lines.push('chcp 65001')
|
||||||
|
}
|
||||||
|
const [ setEnvVarFn, escapeFn ] = isWindows ? [ setEnvVarCmd, escapeParamCmd ] : [ setEnvVarSh, escapeSh ]
|
||||||
|
lines.push(..._.map(environment, setEnvVarFn))
|
||||||
|
lines.push([ command, ...argv ].map(escapeFn).join(' '))
|
||||||
|
return lines.join(os.EOL)
|
||||||
|
}
|
||||||
|
|
||||||
|
const elevateScriptWindows = async (path) => {
|
||||||
|
// './nativeModule' imported here as it only exists on windows
|
||||||
|
// TODO: replace this with sudo-prompt once https://github.com/jorangreef/sudo-prompt/issues/96 is fixed
|
||||||
|
const nativeModule = require('./native-module')
|
||||||
|
const elevateAsync = promisify(nativeModule.load('elevator').elevate)
|
||||||
|
|
||||||
|
// '&' needs to be escaped here (but not when written to a .cmd file)
|
||||||
|
const cmd = [ 'cmd', '/c', escapeParamCmd(path).replace(/&/g, '^&') ]
|
||||||
|
const { cancelled } = await elevateAsync(cmd)
|
||||||
|
return { cancelled }
|
||||||
|
}
|
||||||
|
|
||||||
|
const elevateScriptUnix = async (path, name) => {
|
||||||
|
const cmd = [ 'sh', escapeSh(path) ].join(' ')
|
||||||
|
const [ , stderr ] = await sudoPrompt.execAsync(cmd, { name })
|
||||||
|
if (!_.isEmpty(stderr)) {
|
||||||
|
throw errors.createError({ title: stderr })
|
||||||
|
}
|
||||||
|
return { cancelled: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,73 +178,41 @@ const quoteString = (string) => {
|
|||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
exports.elevateCommand = (command, options) => {
|
exports.elevateCommand = async (command, options) => {
|
||||||
const isWindows = os.platform() === 'win32'
|
const isWindows = os.platform() === 'win32'
|
||||||
|
const launchScript = exports.createLaunchScript(command[0], command.slice(1), options.environment)
|
||||||
const prefixedCommand = _.concat(
|
return Bluebird.using(tmpFileDisposer({ postfix: '.cmd' }), async ({ path }) => {
|
||||||
exports.getEnvironmentCommandPrefix(options.environment),
|
await writeFileAsync(path, launchScript)
|
||||||
_.map(command, (string) => {
|
if (isWindows) {
|
||||||
return isWindows ? quoteString(string) : string
|
return elevateScriptWindows(path)
|
||||||
})
|
}
|
||||||
)
|
try {
|
||||||
|
return await elevateScriptUnix(path, options.applicationName)
|
||||||
if (isWindows) {
|
} catch (error) {
|
||||||
const elevator = Bluebird.promisifyAll(nativeModule.load('elevator'))
|
// We're hardcoding internal error messages declared by `sudo-prompt`.
|
||||||
return elevator.elevateAsync([
|
// There doesn't seem to be a better way to handle these errors, so
|
||||||
'cmd.exe',
|
// for now, we should make sure we double check if the error messages
|
||||||
'/c',
|
// have changed every time we upgrade `sudo-prompt`.
|
||||||
quoteString(_.join(prefixedCommand, ' '))
|
console.log('error', error)
|
||||||
]).then((results) => {
|
if (_.includes(error.message, 'is not in the sudoers file')) {
|
||||||
return {
|
throw errors.createUserError({
|
||||||
cancelled: results.cancelled
|
title: "Your user doesn't have enough privileges to proceed",
|
||||||
|
description: 'This application requires sudo privileges to be able to write to drives'
|
||||||
|
})
|
||||||
|
} else if (_.startsWith(error.message, 'Command failed:')) {
|
||||||
|
throw errors.createUserError({
|
||||||
|
title: 'The elevated process died unexpectedly',
|
||||||
|
description: `The process error code was ${error.code}`
|
||||||
|
})
|
||||||
|
} else if (error.message === 'User did not grant permission.') {
|
||||||
|
return { cancelled: true }
|
||||||
|
} else if (error.message === 'No polkit authentication agent found.') {
|
||||||
|
throw errors.createUserError({
|
||||||
|
title: 'No polkit authentication agent found',
|
||||||
|
description: 'Please install a polkit authentication agent for your desktop environment of choice to continue'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
throw error
|
||||||
}
|
|
||||||
|
|
||||||
return sudoPrompt.execAsync(commandJoin(prefixedCommand), {
|
|
||||||
name: options.applicationName
|
|
||||||
}).then((stdout, stderr) => {
|
|
||||||
if (!_.isEmpty(stderr)) {
|
|
||||||
throw errors.createError({
|
|
||||||
title: stderr
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
cancelled: false
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're hardcoding internal error messages declared by `sudo-prompt`.
|
|
||||||
// There doesn't seem to be a better way to handle these errors, so
|
|
||||||
// for now, we should make sure we double check if the error messages
|
|
||||||
// have changed every time we upgrade `sudo-prompt`.
|
|
||||||
}).catch((error) => {
|
|
||||||
console.log('error', error.cause)
|
|
||||||
return _.includes(error.message, 'is not in the sudoers file')
|
|
||||||
}, () => {
|
|
||||||
throw errors.createUserError({
|
|
||||||
title: 'Your user doesn\'t have enough privileges to proceed',
|
|
||||||
description: 'This application requires sudo privileges to be able to write to drives'
|
|
||||||
})
|
|
||||||
}).catch((error) => {
|
|
||||||
return _.startsWith(error.message, 'Command failed:')
|
|
||||||
}, (error) => {
|
|
||||||
throw errors.createUserError({
|
|
||||||
title: 'The elevated process died unexpectedly',
|
|
||||||
description: `The process error code was ${error.code}`
|
|
||||||
})
|
|
||||||
}).catch({
|
|
||||||
message: 'User did not grant permission.'
|
|
||||||
}, () => {
|
|
||||||
return {
|
|
||||||
cancelled: true
|
|
||||||
}
|
|
||||||
}).catch({
|
|
||||||
message: 'No polkit authentication agent found.'
|
|
||||||
}, () => {
|
|
||||||
throw errors.createUserError({
|
|
||||||
title: 'No polkit authentication agent found',
|
|
||||||
description: 'Please install a polkit authentication agent for your desktop environment of choice to continue'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
49
lib/shared/utils.js
Normal file → Executable file
49
lib/shared/utils.js
Normal file → Executable file
@ -19,6 +19,8 @@
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const Bluebird = require('bluebird')
|
const Bluebird = require('bluebird')
|
||||||
const request = Bluebird.promisifyAll(require('request'))
|
const request = Bluebird.promisifyAll(require('request'))
|
||||||
|
const tmp = require('tmp')
|
||||||
|
|
||||||
const errors = require('./errors')
|
const errors = require('./errors')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,3 +170,50 @@ exports.getConfig = (configUrl) => {
|
|||||||
return request.getAsync(configUrl, { json: true })
|
return request.getAsync(configUrl, { json: true })
|
||||||
.get('body')
|
.get('body')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary returns { path: String, cleanup: Function }
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @param {Object} options - options
|
||||||
|
*
|
||||||
|
* @returns {Promise<{ path: String, cleanup: Function }>}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* tmpFileAsync()
|
||||||
|
* .then({ path, cleanup } => {
|
||||||
|
* console.log(path)
|
||||||
|
* cleanup()
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const tmpFileAsync = (options) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
tmp.file(options, (error, path, _fd, cleanup) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve({ path, cleanup })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Disposer for tmpFileAsync, calls cleanup()
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @param {Object} options - options
|
||||||
|
*
|
||||||
|
* @returns {Disposer<{ path: String, cleanup: Function }>}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await Bluebird.using(tmpFileDisposer(), ({ path }) => {
|
||||||
|
* console.log(path);
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
exports.tmpFileDisposer = (options) => {
|
||||||
|
return Bluebird.resolve(tmpFileAsync(options))
|
||||||
|
.disposer(({ cleanup }) => {
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
5
npm-shrinkwrap.json
generated
5
npm-shrinkwrap.json
generated
@ -3131,11 +3131,6 @@
|
|||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"command-join": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-Uui5hPSHLZUv8b3IuYOX0nxxRM8="
|
|
||||||
},
|
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
|
||||||
|
@ -49,7 +49,6 @@
|
|||||||
"bluebird": "^3.5.3",
|
"bluebird": "^3.5.3",
|
||||||
"bootstrap-sass": "^3.3.6",
|
"bootstrap-sass": "^3.3.6",
|
||||||
"color": "^2.0.1",
|
"color": "^2.0.1",
|
||||||
"command-join": "^2.0.0",
|
|
||||||
"d3": "^4.13.0",
|
"d3": "^4.13.0",
|
||||||
"debug": "^3.1.0",
|
"debug": "^3.1.0",
|
||||||
"electron-is-running-in-asar": "^1.0.0",
|
"electron-is-running-in-asar": "^1.0.0",
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable quotes */
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const m = require('mochainon')
|
const m = require('mochainon')
|
||||||
@ -21,7 +23,7 @@ const os = require('os')
|
|||||||
const permissions = require('../../lib/shared/permissions')
|
const permissions = require('../../lib/shared/permissions')
|
||||||
|
|
||||||
describe('Shared: permissions', function () {
|
describe('Shared: permissions', function () {
|
||||||
describe('.getEnvironmentCommandPrefix()', function () {
|
describe('.createLaunchScript()', function () {
|
||||||
describe('given windows', function () {
|
describe('given windows', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.osPlatformStub = m.sinon.stub(os, 'platform')
|
this.osPlatformStub = m.sinon.stub(os, 'platform')
|
||||||
@ -32,197 +34,62 @@ describe('Shared: permissions', function () {
|
|||||||
this.osPlatformStub.restore()
|
this.osPlatformStub.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return an empty array if no environment', function () {
|
it('should escape environment variables and arguments', function () {
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix()).to.deep.equal([])
|
m.chai.expect(
|
||||||
})
|
permissions.createLaunchScript(
|
||||||
|
"C:\\Users\\Alice & Bob's Laptop\\\"what\"\\balenaEtcher",
|
||||||
it('should return an empty array if the environment is an empty object', function () {
|
[
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({})).to.deep.equal([])
|
'"a Laser"',
|
||||||
})
|
'arg1',
|
||||||
|
"'&/ ^ \\",
|
||||||
it('should create an environment command prefix out of one variable', function () {
|
'" $ % *'
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
],
|
||||||
FOO: 'bar'
|
{
|
||||||
})).to.deep.equal([
|
key: 'value',
|
||||||
'set',
|
key2: ' " \' ^ & = + $ % / \\',
|
||||||
'FOO=bar&&',
|
key3: 8
|
||||||
'call'
|
}
|
||||||
])
|
)
|
||||||
})
|
).to.equal(
|
||||||
|
`chcp 65001${os.EOL}` +
|
||||||
it('should create an environment command prefix out of many variables', function () {
|
`set "key=value"${os.EOL}` +
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
`set "key2= " ' ^ & = + $ % / \\"${os.EOL}` +
|
||||||
FOO: 'bar',
|
`set "key3=8"${os.EOL}` +
|
||||||
BAR: 'baz',
|
`"C:\\Users\\Alice & Bob's Laptop\\\\"what\\"\\balenaEtcher" "\\"a Laser\\"" "arg1" "'&/ ^ \\" "\\" $ % *"`
|
||||||
BAZ: 'qux'
|
)
|
||||||
})).to.deep.equal([
|
|
||||||
'set',
|
|
||||||
'FOO=bar&&',
|
|
||||||
'set',
|
|
||||||
'BAR=baz&&',
|
|
||||||
'set',
|
|
||||||
'BAZ=qux&&',
|
|
||||||
'call'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should ignore undefined and null variable values', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: null,
|
|
||||||
BAR: 'qux',
|
|
||||||
BAZ: undefined
|
|
||||||
})).to.deep.equal([
|
|
||||||
'set',
|
|
||||||
'BAR=qux&&',
|
|
||||||
'call'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should stringify number values', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 1,
|
|
||||||
BAR: 0,
|
|
||||||
BAZ: -1
|
|
||||||
})).to.deep.equal([
|
|
||||||
'set',
|
|
||||||
'FOO=1&&',
|
|
||||||
'set',
|
|
||||||
'BAR=0&&',
|
|
||||||
'set',
|
|
||||||
'BAZ=-1&&',
|
|
||||||
'call'
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given linux', function () {
|
for (const platform of [ 'linux', 'darwin' ]) {
|
||||||
beforeEach(function () {
|
describe(`given ${platform}`, function () {
|
||||||
this.osPlatformStub = m.sinon.stub(os, 'platform')
|
beforeEach(function () {
|
||||||
this.osPlatformStub.returns('linux')
|
this.osPlatformStub = m.sinon.stub(os, 'platform')
|
||||||
})
|
this.osPlatformStub.returns(platform)
|
||||||
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
this.osPlatformStub.restore()
|
this.osPlatformStub.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return an empty array if no environment', function () {
|
it('should escape environment variables and arguments', function () {
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix()).to.deep.equal([])
|
m.chai.expect(
|
||||||
|
permissions.createLaunchScript(
|
||||||
|
"/home/Alice & Bob's Laptop/\"what\"/balenaEtcher",
|
||||||
|
[ 'arg1', "'&/ ^ \\", '" $ % *' ],
|
||||||
|
{
|
||||||
|
key: 'value',
|
||||||
|
key2: ' " \' ^ & = + $ % / \\',
|
||||||
|
key3: 8
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).to.equal(
|
||||||
|
`export key='value'${os.EOL}` +
|
||||||
|
`export key2=' " '\\'' ^ & = + $ % / \\'${os.EOL}` +
|
||||||
|
`export key3='8'${os.EOL}` +
|
||||||
|
`'/home/Alice & Bob'\\''s Laptop/"what"/balenaEtcher' 'arg1' ''\\''&/ ^ \\' '" $ % *'`
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
it('should return an empty array if the environment is an empty object', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({})).to.deep.equal([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create an environment command prefix out of one variable', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 'bar'
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'FOO=bar'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create an environment command prefix out of many variables', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 'bar',
|
|
||||||
BAR: 'baz',
|
|
||||||
BAZ: 'qux'
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'FOO=bar',
|
|
||||||
'BAR=baz',
|
|
||||||
'BAZ=qux'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should ignore undefined and null variable values', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: null,
|
|
||||||
BAR: 'qux',
|
|
||||||
BAZ: undefined
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'BAR=qux'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should stringify number values', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 1,
|
|
||||||
BAR: 0,
|
|
||||||
BAZ: -1
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'FOO=1',
|
|
||||||
'BAR=0',
|
|
||||||
'BAZ=-1'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given darwin', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.osPlatformStub = m.sinon.stub(os, 'platform')
|
|
||||||
this.osPlatformStub.returns('darwin')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
this.osPlatformStub.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return an empty array if no environment', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix()).to.deep.equal([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return an empty array if the environment is an empty object', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({})).to.deep.equal([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create an environment command prefix out of one variable', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 'bar'
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'FOO=bar'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create an environment command prefix out of many variables', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 'bar',
|
|
||||||
BAR: 'baz',
|
|
||||||
BAZ: 'qux'
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'FOO=bar',
|
|
||||||
'BAR=baz',
|
|
||||||
'BAZ=qux'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should ignore undefined and null variable values', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: null,
|
|
||||||
BAR: 'qux',
|
|
||||||
BAZ: undefined
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'BAR=qux'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should stringify number values', function () {
|
|
||||||
m.chai.expect(permissions.getEnvironmentCommandPrefix({
|
|
||||||
FOO: 1,
|
|
||||||
BAR: 0,
|
|
||||||
BAZ: -1
|
|
||||||
})).to.deep.equal([
|
|
||||||
'env',
|
|
||||||
'FOO=1',
|
|
||||||
'BAR=0',
|
|
||||||
'BAZ=-1'
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user