pax_global_header00006660000000000000000000000064137577716400014533gustar00rootroot0000000000000052 comment=c796c0a9faf36b37f1163df75143cac3680bf34e php-shellcommand-1.6.3/000077500000000000000000000000001375777164000147755ustar00rootroot00000000000000php-shellcommand-1.6.3/.github/000077500000000000000000000000001375777164000163355ustar00rootroot00000000000000php-shellcommand-1.6.3/.github/workflows/000077500000000000000000000000001375777164000203725ustar00rootroot00000000000000php-shellcommand-1.6.3/.github/workflows/tests.yml000066400000000000000000000023311375777164000222560ustar00rootroot00000000000000name: Tests on: pull_request jobs: phpunit: name: PHP ${{ matrix.php }} runs-on: ubuntu-latest strategy: matrix: php: - "5.3" - "5.4" - "5.5" - "5.6" - "7.0" - "7.1" - "7.2" - "7.3" - "7.4" - "8.0" steps: - name: Checkout uses: actions/checkout@v2 - name: Install PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} tools: composer:v2 - name: Update composer run: composer self-update - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install composer packages run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - name: Run phpunit run: vendor/bin/phpunit --color=always php-shellcommand-1.6.3/.gitignore000066400000000000000000000000771375777164000167710ustar00rootroot00000000000000phpunit.xml composer.lock /vendor/ .phpunit.result.cache *.swp php-shellcommand-1.6.3/CHANGELOG.md000066400000000000000000000014161375777164000166100ustar00rootroot00000000000000# CHANGELOG ## 1.2.2 * Issue #16: Command on different drive didn't work on windows ## 1.2.1 * Issue #1: Command with spaces didn't work on windows ## 1.2.0 * Add option to return untrimmed output and error ## 1.1.0 * Issue #7: UTF-8 encoded arguments where truncated ## 1.0.7 * Issue #6: Solve `proc_open()` pipe configuration for both, Windows / Linux ## 1.0.6 * Undid `proc_open()` changes as it broke error capturing ## 1.0.5 * Improve `proc_open()` pipe configuration ## 1.0.4 * Add `$useExec` option to fix Windows issues (#3) ## 1.0.3 * Add `getExecuted()` to find out execution status of the command ## 1.0.2 * Add `$escape` parameter to `addArg()` to override escaping settings per call ## 1.0.1 * Minor fixes ## 1.0.0 * Initial release php-shellcommand-1.6.3/LICENSE000066400000000000000000000020711375777164000160020ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Michael Härtl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-shellcommand-1.6.3/README.md000066400000000000000000000150201375777164000162520ustar00rootroot00000000000000php-shellcommand =========== [![GitHub Tests](https://github.com/mikehaertl/php-shellcommand/workflows/Tests/badge.svg)](https://github.com/mikehaertl/php-shellcommand/actions) [![Packagist Version](https://img.shields.io/packagist/v/mikehaertl/php-shellcommand?label=version)](https://packagist.org/packages/mikehaertl/php-shellcommand) [![Packagist Downloads](https://img.shields.io/packagist/dt/mikehaertl/php-shellcommand)](https://packagist.org/packages/mikehaertl/php-shellcommand) [![GitHub license](https://img.shields.io/github/license/mikehaertl/php-shellcommand)](https://github.com/mikehaertl/php-shellcommand/blob/master/LICENSE) [![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/mikehaertl/php-shellcommand)](https://packagist.org/packages/mikehaertl/php-shellcommand) php-shellcommand provides a simple object oriented interface to execute shell commands. ## Installing ### Prerequisites Your php version must be `5.4` or later. ### Installing with composer This package can be installed easily using composer. ``` composer require mikehaertl/php-shellcommand ``` ## Features * Catches `stdOut`, `stdErr` and `exitCode` * Handle argument escaping * Pass environment vars and other options to `proc_open()` ## Examples ### Basic Example ```php execute()) { echo $command->getOutput(); } else { echo $command->getError(); $exitCode = $command->getExitCode(); } ``` ### Advanced Features ```php // Create command with options array $command = new Command(array( 'command' => '/usr/local/bin/mycommand', // Will be passed as environment variables to the command 'procEnv' => array( 'DEMOVAR' => 'demovalue' ), // Will be passed as options to proc_open() 'procOptions' => array( 'bypass_shell' => true, ), )); // Add arguments with correct escaping: // results in --name='d'\''Artagnan' $command->addArg('--name=', "d'Artagnan"); // Add argument with several values // results in --keys key1 key2 $command->addArg('--keys', array('key1','key2')); // Add string to pipe to command on standard input $command->setStdIn('string'); ``` ## API ### Properties * `$escapeArgs`: Whether to escape any argument passed through `addArg()`. Default is `true`. * `$escapeCommand`: Whether to escape the command passed to `setCommand()` or the constructor. This is only useful if `$escapeArgs` is `false`. Default is `false`. * `$useExec`: Whether to use `exec()` instead of `proc_open()`. This is a workaround for OS which have problems with `proc_open()`. Default is `false`. * `$captureStdErr`: Whether to capture stderr when `useExec` is set. This will try to redirect the otherwhise unavailable `stderr` to `stdout`, so that both have the same content on error. Default is `true`. * `$procCwd`: The initial working dir passed to `proc_open()`. Default is `null` for current PHP working dir. * `$procEnv`: An array with environment variables to pass to `proc_open()`. Default is `null` for none. * `$procOptions`: An array of `other_options` for `proc_open()`. Default is `null` for none. * `$nonBlockingMode`: Whether to set the stdin/stdout/stderr streams to non-blocking mode when `proc_open()` is used. This allows to have huge inputs/outputs without making the process hang. The default is `null` which will enable the feature on Non-Windows systems. Set it to `true` or `false` to manually enable/disable it. Note that it doesn't work on Windows. * `$timeout`: The time in seconds after which the command should be terminated. This only works in non-blocking mode. Default is `null` which means the process is never terminated. * `$locale`: The locale to (temporarily) set with `setlocale()` before running the command. This can be set to e.g. `en_US.UTF-8` if you have issues with UTF-8 encoded arguments. You can configure all these properties via an array that you pass in the constructor. You can also pass `command`, `execCommand` and `args` as options. This will call the respective setter (`setCommand()`, `setExecCommand()`, etc.). ### Methods * `__construct($options = null)` * `$options`: either a command string or an options array (see `setOptions()`) * `setOptions($options)`: Set command options * `$options`: array of name => value options that should be applied to the object. You can also pass options that use a setter, e.g. you can pass a `command` option which will be passed to `setCommand().` * `setCommand($command)`: Set command * `$command`: The command or full command string to execute, like `gzip` or `gzip -d`. You can still call `addArg()` to add more arguments to the command. If `$escapeCommand` was set to `true`, the command gets escaped through `escapeshellcmd()`. * `getCommand()`: The command that was set through `setCommand()` or passed to the constructor. * `getExecCommand()`: The full command string to execute. * `setArgs($args)`: Set argument as string * `$args`: The command arguments as string. Note, that these will not get escaped! * `getArgs()`: The command arguments that where set through `setArgs()` or `addArg()`, as string * `addArg($key, $value=null, $escape=null)`: Add argument with correct escaping * `$key`: The argument key to add e.g. `--feature` or `--name=`. If the key does not end with and `=`, the `$value` will be separated by a space, if any. Keys are not escaped unless `$value` is null and `$escape` is `true`. * `$value`: The optional argument value which will get escaped if `$escapeArgs` is true. An array can be passed to add more than one value for a key, e.g. `addArg('--exclude', array('val1','val2'))` which will create the option "--exclude 'val1' 'val2'". * `$escape`: If set, this overrides the `$escapeArgs` setting and enforces escaping/no escaping * `setStdIn()`: String or resource to supply to command via standard input. * `getOutput()`: The command output as string. Empty if none. * `getError()`: The error message, either stderr or internal message. Empty if no error. * `getStdErr()`: The stderr output. Empty if none. * `getExitCode()`: The exit code. * `getExecuted()`: Whether the command was successfully executed. * `execute()`: Executes the command and returns `true` on success, `false` otherwhise. > **Note:** `getError()`, `getStdErr()` and `getOutput()` return the trimmed output. > You can pass `false` to these methods if you need any possible line breaks at the end. php-shellcommand-1.6.3/composer.json000066400000000000000000000010171375777164000175160ustar00rootroot00000000000000{ "name": "mikehaertl/php-shellcommand", "description": "An object oriented interface to shell commands", "keywords": ["shell"], "license": "MIT", "authors": [ { "name": "Michael Härtl", "email": "haertl.mike@gmail.com" } ], "require": { "php": ">= 5.3.0" }, "require-dev": { "phpunit/phpunit": ">4.0 <=9.4" }, "autoload": { "psr-4": { "mikehaertl\\shellcommand\\": "src/" } }, "autoload-dev": { "psr-4": { "tests\\": "tests" } } } php-shellcommand-1.6.3/phpunit.xml.dist000066400000000000000000000010341375777164000201460ustar00rootroot00000000000000 ./tests/ php-shellcommand-1.6.3/src/000077500000000000000000000000001375777164000155645ustar00rootroot00000000000000php-shellcommand-1.6.3/src/Command.php000066400000000000000000000463241375777164000176640ustar00rootroot00000000000000 * @license http://www.opensource.org/licenses/MIT */ class Command { /** * @var bool whether to escape any argument passed through `addArg()`. * Default is `true`. */ public $escapeArgs = true; /** * @var bool whether to escape the command passed to `setCommand()` or the * constructor. This is only useful if `$escapeArgs` is `false`. Default * is `false`. */ public $escapeCommand = false; /** * @var bool whether to use `exec()` instead of `proc_open()`. This can be * used on Windows system to workaround some quirks there. Note, that any * errors from your command will be output directly to the PHP output * stream. `getStdErr()` will also not work anymore and thus you also won't * get the error output from `getError()` in this case. You also can't pass * any environment variables to the command if this is enabled. Default is * `false`. */ public $useExec = false; /** * @var bool whether to capture stderr (2>&1) when `useExec` is true. This * will try to redirect the stderr to stdout and provide the complete * output of both in `getStdErr()` and `getError()`. Default is `true`. */ public $captureStdErr = true; /** * @var string|null the initial working dir for `proc_open()`. Default is * `null` for current PHP working dir. */ public $procCwd; /** * @var array|null an array with environment variables to pass to * `proc_open()`. Default is `null` for none. */ public $procEnv; /** * @var array|null an array of other_options for `proc_open()`. Default is * `null` for none. */ public $procOptions; /** * @var bool|null whether to set the stdin/stdout/stderr streams to * non-blocking mode when `proc_open()` is used. This allows to have huge * inputs/outputs without making the process hang. The default is `null` * which will enable the feature on Non-Windows systems. Set it to `true` * or `false` to manually enable/disable it. It does not work on Windows. */ public $nonBlockingMode; /** * @var int the time in seconds after which a command should be terminated. * This only works in non-blocking mode. Default is `null` which means the * process is never terminated. */ public $timeout; /** * @var null|string the locale to temporarily set before calling * `escapeshellargs()`. Default is `null` for none. */ public $locale; /** * @var null|string|resource to pipe to standard input */ protected $_stdIn; /** * @var string the command to execute */ protected $_command; /** * @var array the list of command arguments */ protected $_args = array(); /** * @var string the full command string to execute */ protected $_execCommand; /** * @var string the stdout output */ protected $_stdOut = ''; /** * @var string the stderr output */ protected $_stdErr = ''; /** * @var int the exit code */ protected $_exitCode; /** * @var string the error message */ protected $_error = ''; /** * @var bool whether the command was successfully executed */ protected $_executed = false; /** * @param string|array $options either a command string or an options array * @see setOptions */ public function __construct($options = null) { if (is_array($options)) { $this->setOptions($options); } elseif (is_string($options)) { $this->setCommand($options); } } /** * @param array $options array of name => value options that should be * applied to the object You can also pass options that use a setter, e.g. * you can pass a `fileName` option which will be passed to * `setFileName()`. * @throws \Exception * @return static for method chaining */ public function setOptions($options) { foreach ($options as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } else { $method = 'set'.ucfirst($key); if (method_exists($this, $method)) { call_user_func(array($this,$method), $value); } else { throw new \Exception("Unknown configuration option '$key'"); } } } return $this; } /** * @param string $command the command or full command string to execute, * like 'gzip' or 'gzip -d'. You can still call addArg() to add more * arguments to the command. If $escapeCommand was set to true, the command * gets escaped with escapeshellcmd(). * @return static for method chaining */ public function setCommand($command) { if ($this->escapeCommand) { $command = escapeshellcmd($command); } if ($this->getIsWindows()) { // Make sure to switch to correct drive like "E:" first if we have // a full path in command if (isset($command[1]) && $command[1]===':') { $position = 1; // Could be a quoted absolute path because of spaces. // i.e. "C:\Program Files (x86)\file.exe" } elseif (isset($command[2]) && $command[2]===':') { $position = 2; } else { $position = false; } // Absolute path. If it's a relative path, let it slide. if ($position) { $command = sprintf( $command[$position - 1] . ': && cd %s && %s', escapeshellarg(dirname($command)), escapeshellarg(basename($command)) ); } } $this->_command = $command; return $this; } /** * @param string|resource $stdIn If set, the string will be piped to the * command via standard input. This enables the same functionality as * piping on the command line. It can also be a resource like a file * handle or a stream in which case its content will be piped into the * command like an input redirection. * @return static for method chaining */ public function setStdIn($stdIn) { $this->_stdIn = $stdIn; return $this; } /** * @return string|null the command that was set through setCommand() or * passed to the constructor. `null` if none. */ public function getCommand() { return $this->_command; } /** * @return string|bool the full command string to execute. If no command * was set with setCommand() or passed to the constructor it will return * `false`. */ public function getExecCommand() { if ($this->_execCommand===null) { $command = $this->getCommand(); if (!$command) { $this->_error = 'Could not locate any executable command'; return false; } $args = $this->getArgs(); $this->_execCommand = $args ? $command.' '.$args : $command; } return $this->_execCommand; } /** * @param string $args the command arguments as string. Note that these * will not get escaped! * @return static for method chaining */ public function setArgs($args) { $this->_args = array($args); return $this; } /** * @return string the command args that where set with setArgs() or added * with addArg() separated by spaces */ public function getArgs() { return implode(' ', $this->_args); } /** * @param string $key the argument key to add e.g. `--feature` or * `--name=`. If the key does not end with and `=`, the $value will be * separated by a space, if any. Keys are not escaped unless $value is null * and $escape is `true`. * @param string|array|null $value the optional argument value which will * get escaped if $escapeArgs is true. An array can be passed to add more * than one value for a key, e.g. `addArg('--exclude', * array('val1','val2'))` which will create the option `'--exclude' 'val1' * 'val2'`. * @param bool|null $escape if set, this overrides the $escapeArgs setting * and enforces escaping/no escaping * @return static for method chaining */ public function addArg($key, $value = null, $escape = null) { $doEscape = $escape !== null ? $escape : $this->escapeArgs; $useLocale = $doEscape && $this->locale !== null; if ($useLocale) { $locale = setlocale(LC_CTYPE, 0); // Returns current locale setting setlocale(LC_CTYPE, $this->locale); } if ($value === null) { $this->_args[] = $doEscape ? escapeshellarg($key) : $key; } else { if (substr($key, -1) === '=') { $separator = '='; $argKey = substr($key, 0, -1); } else { $separator = ' '; $argKey = $key; } $argKey = $doEscape ? escapeshellarg($argKey) : $argKey; if (is_array($value)) { $params = array(); foreach ($value as $v) { $params[] = $doEscape ? escapeshellarg($v) : $v; } $this->_args[] = $argKey . $separator . implode(' ', $params); } else { $this->_args[] = $argKey . $separator . ($doEscape ? escapeshellarg($value) : $value); } } if ($useLocale) { setlocale(LC_CTYPE, $locale); } return $this; } /** * @param bool $trim whether to `trim()` the return value. The default is `true`. * @return string the command output (stdout). Empty if none. */ public function getOutput($trim = true) { return $trim ? trim($this->_stdOut) : $this->_stdOut; } /** * @param bool $trim whether to `trim()` the return value. The default is `true`. * @return string the error message, either stderr or an internal message. * Empty string if none. */ public function getError($trim = true) { return $trim ? trim($this->_error) : $this->_error; } /** * @param bool $trim whether to `trim()` the return value. The default is `true`. * @return string the stderr output. Empty if none. */ public function getStdErr($trim = true) { return $trim ? trim($this->_stdErr) : $this->_stdErr; } /** * @return int|null the exit code or null if command was not executed yet */ public function getExitCode() { return $this->_exitCode; } /** * @return string whether the command was successfully executed */ public function getExecuted() { return $this->_executed; } /** * Execute the command * * @return bool whether execution was successful. If `false`, error details * can be obtained from getError(), getStdErr() and getExitCode(). */ public function execute() { $command = $this->getExecCommand(); if (!$command) { return false; } if ($this->useExec) { $execCommand = $this->captureStdErr ? "$command 2>&1" : $command; exec($execCommand, $output, $this->_exitCode); $this->_stdOut = implode("\n", $output); if ($this->_exitCode !== 0) { $this->_stdErr = $this->_stdOut; $this->_error = empty($this->_stdErr) ? 'Command failed' : $this->_stdErr; return false; } } else { $isInputStream = $this->_stdIn !== null && is_resource($this->_stdIn) && in_array(get_resource_type($this->_stdIn), array('file', 'stream')); $isInputString = is_string($this->_stdIn); $hasInput = $isInputStream || $isInputString; $hasTimeout = $this->timeout !== null && $this->timeout > 0; $descriptors = array( 1 => array('pipe','w'), 2 => array('pipe', $this->getIsWindows() ? 'a' : 'w'), ); if ($hasInput) { $descriptors[0] = array('pipe', 'r'); } // Issue #20 Set non-blocking mode to fix hanging processes $nonBlocking = $this->nonBlockingMode === null ? !$this->getIsWindows() : $this->nonBlockingMode; $startTime = $hasTimeout ? time() : 0; $process = proc_open($command, $descriptors, $pipes, $this->procCwd, $this->procEnv, $this->procOptions); if (is_resource($process)) { if ($nonBlocking) { stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); if ($hasInput) { $writtenBytes = 0; $isInputOpen = true; stream_set_blocking($pipes[0], false); if ($isInputStream) { stream_set_blocking($this->_stdIn, false); } } // Due to the non-blocking streams we now have to check in // a loop if the process is still running. We also need to // ensure that all the pipes are written/read alternately // until there's nothing left to write/read. $isRunning = true; while ($isRunning) { $status = proc_get_status($process); $isRunning = $status['running']; // We first write to stdIn if we have an input. For big // inputs it will only write until the input buffer of // the command is full (the command may now wait that // we read the output buffers - see below). So we may // have to continue writing in another cycle. // // After everything is written it's safe to close the // input pipe. if ($isRunning && $hasInput && $isInputOpen) { if ($isInputStream) { $written = stream_copy_to_stream($this->_stdIn, $pipes[0], 16 * 1024, $writtenBytes); if ($written === false || $written === 0) { $isInputOpen = false; fclose($pipes[0]); } else { $writtenBytes += $written; } } else { if ($writtenBytes < strlen($this->_stdIn)) { $writtenBytes += fwrite($pipes[0], substr($this->_stdIn, $writtenBytes)); } else { $isInputOpen = false; fclose($pipes[0]); } } } // Read out the output buffers because if they are full // the command may block execution. We do this even if // $isRunning is `false`, because there could be output // left in the buffers. // // The latter is only an assumption and needs to be // verified - but it does not hurt either and works as // expected. // while (($out = fgets($pipes[1])) !== false) { $this->_stdOut .= $out; } while (($err = fgets($pipes[2])) !== false) { $this->_stdErr .= $err; } $runTime = $hasTimeout ? time() - $startTime : 0; if ($isRunning && $hasTimeout && $runTime >= $this->timeout) { // Only send a SIGTERM and handle status in the next cycle proc_terminate($process); } if (!$isRunning) { $this->_exitCode = $status['exitcode']; if ($this->_exitCode !== 0 && empty($this->_stdErr)) { if ($status['stopped']) { $signal = $status['stopsig']; $this->_stdErr = "Command stopped by signal $signal"; } elseif ($status['signaled']) { $signal = $status['termsig']; $this->_stdErr = "Command terminated by signal $signal"; } else { $this->_stdErr = 'Command unexpectedly terminated without error message'; } } fclose($pipes[1]); fclose($pipes[2]); proc_close($process); } else { // The command is still running. Let's wait some // time before we start the next cycle. usleep(10000); } } } else { if ($hasInput) { if ($isInputStream) { stream_copy_to_stream($this->_stdIn, $pipes[0]); } elseif ($isInputString) { fwrite($pipes[0], $this->_stdIn); } fclose($pipes[0]); } $this->_stdOut = stream_get_contents($pipes[1]); $this->_stdErr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); $this->_exitCode = proc_close($process); } if ($this->_exitCode !== 0) { $this->_error = $this->_stdErr ? $this->_stdErr : "Failed without error message: $command (Exit code: {$this->_exitCode})"; return false; } } else { $this->_error = "Could not run command $command"; return false; } } $this->_executed = true; return true; } /** * @return bool whether we are on a Windows OS */ public function getIsWindows() { return strncasecmp(PHP_OS, 'WIN', 3)===0; } /** * @return string the current command string to execute */ public function __toString() { return (string) $this->getExecCommand(); } } php-shellcommand-1.6.3/tests/000077500000000000000000000000001375777164000161375ustar00rootroot00000000000000php-shellcommand-1.6.3/tests/BlockingCommandTest.php000066400000000000000000000125721375777164000225460ustar00rootroot00000000000000assertEquals('/bin/ls -l', $command->getExecCommand()); $this->assertNull($command->nonBlockingMode); } // Options public function testCanSetOptions() { $command = new Command; $command->setOptions(array( 'command' => 'echo', 'escapeArgs' => false, 'procEnv' => array('TESTVAR' => 'test'), 'args' => '-n $TESTVAR', 'nonBlockingMode' => false, )); $this->assertFalse($command->nonBlockingMode); $this->assertEquals('echo -n $TESTVAR', $command->getExecCommand()); $this->assertFalse($command->escapeArgs); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals('test', $command->getOutput()); } public function testCanPassOptionsToConstructor() { $command = new Command(array( 'command' => 'echo', 'args' => '-n $TESTVAR', 'escapeArgs' => false, 'procEnv' => array('TESTVAR' => 'test'), 'nonBlockingMode' => false, )); $this->assertFalse($command->nonBlockingMode); $this->assertEquals('echo -n $TESTVAR', $command->getExecCommand()); $this->assertFalse($command->escapeArgs); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals('test', $command->getOutput()); } public function testCanRunCommandWithArguments() { $command = new Command('ls'); $command->nonBlockingMode = false; $command->addArg('-l'); $command->addArg('-n'); $this->assertEquals("ls '-l' '-n'", $command->getExecCommand()); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); } // Output / error / exit code public function testCanRunValidCommand() { $dir = __DIR__; $command = new Command("/bin/ls $dir/Command*"); $command->nonBlockingMode = false; $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals("$dir/CommandTest.php", $command->getOutput()); $this->assertEquals("$dir/CommandTest.php\n", $command->getOutput(false)); $this->assertEmpty($command->getError()); $this->assertEmpty($command->getStdErr()); $this->assertEquals(0, $command->getExitCode()); } public function testCanNotRunEmptyCommand() { $command = new Command(''); $command->nonBlockingMode = false; $this->assertFalse($command->execute()); $this->assertEquals('Could not locate any executable command', $command->getError()); } public function testCanNotRunNotExistantCommand() { $command = new Command('/does/not/exist'); $command->nonBlockingMode = false; $this->assertFalse($command->getExecuted()); $this->assertFalse($command->execute()); $this->assertFalse($command->getExecuted()); $this->assertNotEmpty($command->getError()); $this->assertNotEmpty($command->getStdErr()); $this->assertEmpty($command->getOutput()); $this->assertEquals(127, $command->getExitCode()); } public function testCanNotRunInvalidCommand() { $command = new Command('ls --this-does-not-exist'); $command->nonBlockingMode = false; $this->assertFalse($command->getExecuted()); $this->assertFalse($command->execute()); $this->assertFalse($command->getExecuted()); $this->assertNotEmpty($command->getError()); $this->assertNotEmpty($command->getStdErr()); $this->assertEmpty($command->getOutput()); $this->assertEquals(2, $command->getExitCode()); } // Proc public function testCanProvideProcEnvVars() { $command = new Command('echo $TESTVAR'); $command->nonBlockingMode = false; $command->procEnv = array('TESTVAR' => 'testvalue'); $this->assertTrue($command->execute()); $this->assertEquals("testvalue", $command->getOutput()); } public function testCanProvideProcDir() { $tmpDir = sys_get_temp_dir(); $command = new Command('pwd'); $command->procCwd = $tmpDir; $command->nonBlockingMode = false; $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals($tmpDir, $command->getOutput()); } public function testCanRunCommandWithStandardInput() { $command = new Command('/bin/cat'); $command->nonBlockingMode = false; $command->addArg('-T'); $command->setStdIn("\t"); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals("^I", $command->getOutput()); } } php-shellcommand-1.6.3/tests/CommandTest.php000066400000000000000000000301051375777164000210650ustar00rootroot00000000000000assertEquals('/bin/ls -l', $command->getExecCommand()); } public function testCanPassCommandOptionToConstructor() { $command = new Command(array( 'command' => '/bin/ls -l', )); $this->assertEquals('/bin/ls -l', $command->getExecCommand()); } public function testCanSetCommand() { $command = new Command(); $command->setCommand('/bin/ls -l'); $this->assertEquals('/bin/ls -l', $command->getExecCommand()); } // Options public function testCanSetOptions() { $command = new Command; $command->setOptions(array( 'command' => 'echo', 'escapeArgs' => false, 'procEnv' => array('TESTVAR' => 'test'), 'args' => '-n $TESTVAR', )); $this->assertEquals('echo -n $TESTVAR', $command->getExecCommand()); $this->assertFalse($command->escapeArgs); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals('test', $command->getOutput()); } public function testCanPassOptionsToConstructor() { $command = new Command(array( 'command' => 'echo', 'args' => '-n $TESTVAR', 'escapeArgs' => false, 'procEnv' => array('TESTVAR' => 'test'), )); $this->assertEquals('echo -n $TESTVAR', $command->getExecCommand()); $this->assertFalse($command->escapeArgs); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals('test', $command->getOutput()); } // Arguments public function testCanAddArguments() { $command = new Command(array( 'locale' => 'en_US.UTF-8', )); $command->setCommand('test'); $command->setArgs('--arg1=x'); $command->addArg('--a'); $command->addArg('--a', '中文字äüp'); $command->addArg('--a', array("v'1",'v2','v3')); $command->addArg('-b=','v', false); $command->addArg('-b=', array('v4','v5','v6')); $command->addArg('-c', ''); $command->addArg('some name', null, true); $this->assertEquals("--arg1=x '--a' '--a' '中文字äüp' '--a' 'v'\''1' 'v2' 'v3' -b=v '-b'='v4' 'v5' 'v6' '-c' '' 'some name'", $command->getArgs()); $this->assertEquals("test --arg1=x '--a' '--a' '中文字äüp' '--a' 'v'\''1' 'v2' 'v3' -b=v '-b'='v4' 'v5' 'v6' '-c' '' 'some name'", $command->getExecCommand()); } public function testCanResetArguments() { $command = new Command(); $command->addArg('--demo'); $command->addArg('-name=test'); $command->setArgs('--arg1=x'); $this->assertEquals("--arg1=x", $command->getArgs()); } public function testCanDisableEscaping() { $command = new Command(); $command->escapeArgs = false; $command->addArg('--a'); $command->addArg('--a', 'v'); $command->addArg('--a', array("v1",'v2','v3')); $command->addArg('-b=','v', true); $command->addArg('-b=', array('v4','v5','v6')); $command->addArg('some name', null, true); $this->assertEquals("--a --a v --a v1 v2 v3 '-b'='v' -b=v4 v5 v6 'some name'", $command->getArgs()); } public function testCanPreventCommandInjection() { $command = new Command(array( 'command' => 'curl', )); $command->addArg('http://example.com --wrong-argument || echo "RCE 1"'); $this->assertEquals("'http://example.com --wrong-argument || echo \"RCE 1\"'", $command->getArgs()); $command = new Command(array( 'command' => 'curl', )); $command->addArg('http://example.com'); $command->addArg('--header foo --wrong-argument || echo "RCE 2" ||', 'bar'); $this->assertEquals("'http://example.com' '--header foo --wrong-argument || echo \"RCE 2\" ||' 'bar'", $command->getArgs()); } public function testCanRunCommandWithArguments() { $command = new Command('ls'); $command->addArg('-l'); $command->addArg('-n'); $this->assertEquals("ls '-l' '-n'", $command->getExecCommand()); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); } // Output / error / exit code public function testCanRunValidCommand() { $dir = __DIR__; $command = new Command("/bin/ls $dir/Command*"); $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals("$dir/CommandTest.php", $command->getOutput()); $this->assertEquals("$dir/CommandTest.php\n", $command->getOutput(false)); $this->assertEmpty($command->getError()); $this->assertEmpty($command->getStdErr()); $this->assertEquals(0, $command->getExitCode()); } public function testCanNotRunEmptyCommand() { $command = new Command(''); $this->assertFalse($command->execute()); $this->assertEquals('Could not locate any executable command', $command->getError()); } public function testCanNotRunNotExistantCommand() { $command = new Command('/does/not/exist'); $this->assertFalse($command->getExecuted()); $this->assertFalse($command->execute()); $this->assertFalse($command->getExecuted()); $this->assertNotEmpty($command->getError()); $this->assertNotEmpty($command->getStdErr()); $this->assertEmpty($command->getOutput()); $this->assertEquals(127, $command->getExitCode()); } public function testCanNotRunInvalidCommand() { $command = new Command('ls --this-does-not-exist'); $this->assertFalse($command->getExecuted()); $this->assertFalse($command->execute()); $this->assertFalse($command->getExecuted()); $this->assertNotEmpty($command->getError()); $this->assertNotEmpty($command->getStdErr()); $this->assertEmpty($command->getOutput()); $this->assertEquals(2, $command->getExitCode()); } public function testCanCastToString() { $command = new Command('ls'); $command->addArg('-l'); $command->addArg('-n'); $this->assertEquals("ls '-l' '-n'", (string)$command); } // Exec public function testCanRunValidCommandWithExec() { $dir = __DIR__; $command = new Command("/bin/ls $dir/Command*"); $command->useExec = true; $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals("$dir/CommandTest.php", $command->getOutput()); $this->assertEmpty($command->getError()); $this->assertEmpty($command->getStdErr()); $this->assertEquals(0, $command->getExitCode()); } public function testCanNotRunNotExistantCommandWithExec() { $command = new Command('/does/not/exist'); $command->useExec = true; $this->assertFalse($command->getExecuted()); $this->assertFalse($command->execute()); $this->assertFalse($command->getExecuted()); $this->assertNotEmpty($command->getError()); $this->assertNotEmpty($command->getStdErr()); $this->assertNotEmpty($command->getOutput()); $this->assertEquals(127, $command->getExitCode()); } public function testCanNotRunInvalidCommandWithExec() { $command = new Command('ls --this-does-not-exist'); $command->useExec = true; $this->assertFalse($command->getExecuted()); $this->assertFalse($command->execute()); $this->assertFalse($command->getExecuted()); $this->assertNotEmpty($command->getError()); $this->assertNotEmpty($command->getStdErr()); $this->assertNotEmpty($command->getOutput()); $this->assertEquals(2, $command->getExitCode()); } // Proc public function testCanProvideProcEnvVars() { $command = new Command('echo $TESTVAR'); $command->procEnv = array('TESTVAR' => 'testvalue'); $this->assertTrue($command->execute()); $this->assertEquals("testvalue", $command->getOutput()); } public function testCanProvideProcDir() { $tmpDir = sys_get_temp_dir(); $command = new Command('pwd'); $command->procCwd = $tmpDir; $this->assertFalse($command->getExecuted()); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals($tmpDir, $command->getOutput()); } public function testCanRunCommandWithStandardInput() { $command = new Command('/bin/cat'); $command->addArg('-T'); $command->setStdIn("\t"); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals("^I", $command->getOutput()); } public function testCanRunCommandWithStandardInputStream() { $string = str_repeat('01234567890abcdef', 16 * 1024); // 16 * 16 * 1024 = 256KB $tmpfile = tmpfile(); fwrite($tmpfile, $string); fseek($tmpfile, 0); $command = new Command('/bin/cat'); $command->setStdIn($tmpfile); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals(strlen($string), strlen($command->getOutput())); fclose($tmpfile); } public function testCanRunCommandWithBigInputAndOutput() { $string = str_repeat('01234567890abcdef', 16 * 1024); // 16 * 16 * 1024 = 256KB $command = new Command('/bin/cat'); $command->setStdIn($string); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $this->assertEquals(strlen($string), strlen($command->getOutput())); } public function testCanRunLongRunningCommandWithBigInputAndOutput() { $string = str_repeat('01234567890abcdef', 16 * 1024); // 16 * 16 * 1024 = 256KB $command = new Command('/bin/cat; echo "start" ; sleep 2 ; echo "done"'); $command->setStdIn($string); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $expected = $string . "start\ndone"; $this->assertEquals(strlen($expected), strlen($command->getOutput())); } public function testCanRunLongRunningCommandWithStandardInputStream() { $string = str_repeat('01234567890abcdef', 16 * 1024); // 16 * 16 * 1024 = 256KB $tmpfile = tmpfile(); fwrite($tmpfile, $string); fseek($tmpfile, 0); $command = new Command('/bin/cat; echo "start" ; sleep 2 ; echo "done"'); $command->setStdIn($tmpfile); $this->assertTrue($command->execute()); $this->assertTrue($command->getExecuted()); $expected = $string . "start\ndone"; $this->assertEquals(strlen($expected), strlen($command->getOutput())); fclose($tmpfile); } public function testCanTerminateLongRunningCommandWithTimeout() { $command = new Command('sleep 5'); $command->timeout = 2; $startTime = time(); $this->assertFalse($command->execute()); $stopTime = time(); $this->assertFalse($command->getExecuted()); $this->assertNotEquals(0, $command->getExitCode()); $this->assertStringStartsWith('Command terminated by signal', $command->getError()); $this->assertStringStartsWith('Command terminated by signal', $command->getStdErr()); $this->assertEmpty($command->getOutput()); $this->assertEquals(2, $stopTime - $startTime); } } php-shellcommand-1.6.3/tests/bootstrap.php000066400000000000000000000013231375777164000206640ustar00rootroot00000000000000', '<', etc. * @param string $version the version to check against * @return bool whether PHPUnit matches the version to check */ function phpUnitVersion($operator, $version) { $phpUnitVersion = class_exists('\PHPUnit\Runner\Version') ? call_user_func(array('\PHPUnit\Runner\Version', 'id')) : call_user_func(array('\PHPUnit_Runner_Version', 'id')); return version_compare($phpUnitVersion, $version, $operator); } require __DIR__ . '/../vendor/autoload.php'; // Default in some installations setlocale(LC_CTYPE, 'C');