diff --git a/src/Symfony/Component/Process/CHANGELOG.md b/src/Symfony/Component/Process/CHANGELOG.md index 7fa5b72d50..8a3f631a4e 100644 --- a/src/Symfony/Component/Process/CHANGELOG.md +++ b/src/Symfony/Component/Process/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * added ProcessBuilder::setArguments() to reset the arguments on a builder * added a way to retrieve the standard and error output incrementally * added Process:restart() + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows 2.1.0 ----- diff --git a/src/Symfony/Component/Process/ProcessBuilder.php b/src/Symfony/Component/Process/ProcessBuilder.php index 1a95bd0df4..45a5cd1d40 100644 --- a/src/Symfony/Component/Process/ProcessBuilder.php +++ b/src/Symfony/Component/Process/ProcessBuilder.php @@ -143,7 +143,7 @@ class ProcessBuilder $options = $this->options; - $script = implode(' ', array_map('escapeshellarg', $this->arguments)); + $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $this->arguments)); if ($this->inheritEnv) { $env = $this->env ? $this->env + $_ENV : null; diff --git a/src/Symfony/Component/Process/ProcessUtils.php b/src/Symfony/Component/Process/ProcessUtils.php new file mode 100644 index 0000000000..73380be84d --- /dev/null +++ b/src/Symfony/Component/Process/ProcessUtils.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin HasoĊˆ + */ +class ProcessUtils +{ + /** + * This class should not be instantiated + */ + private function __construct() + { + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + */ + public static function escapeArgument($argument) + { + //Fix for PHP bug #43784 escapeshellarg removes % from given string + //Fix for PHP bug #49446 escapeshellarg dosn`t work on windows + //@see https://bugs.php.net/bug.php?id=43784 + //@see https://bugs.php.net/bug.php?id=49446 + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $escapedArgument = ''; + foreach(preg_split('/([%"])/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' == $part) { + $escapedArgument .= '\\"'; + } elseif ('%' == $part) { + $escapedArgument .= '^%'; + } else { + $escapedArgument .= escapeshellarg($part); + } + } + + return $escapedArgument; + } + + return escapeshellarg($argument); + } +} diff --git a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php index 38e1491090..585aa9f137 100644 --- a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php @@ -115,4 +115,16 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase $this->assertContains("second", $proc->getCommandLine()); } + + public function testShouldEscapeArguments() + { + $pb = new ProcessBuilder(array('%path%', 'foo " bar')); + $proc = $pb->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertSame('^%"path"^% "foo "\\"" bar"', $proc->getCommandLine()); + } else { + $this->assertSame("'%path%' 'foo \" bar'", $proc->getCommandLine()); + } + } } diff --git a/src/Symfony/Component/Process/Tests/ProcessUtilsTest.php b/src/Symfony/Component/Process/Tests/ProcessUtilsTest.php new file mode 100644 index 0000000000..e51da5a5e2 --- /dev/null +++ b/src/Symfony/Component/Process/Tests/ProcessUtilsTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ProcessUtils; + +class ProcessUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider dataArguments + */ + public function testEscapeArgument($result, $argument) + { + $this->assertSame($result, ProcessUtils::escapeArgument($argument)); + } + + public function dataArguments() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return array( + array('"foo bar"', 'foo bar'), + array('^%"path"^%', '%path%'), + array('"<|>"\\"" "\\""\'f"', '<|>" "\'f'), + ); + } + + return array( + array("'foo bar'", 'foo bar'), + array("'%path%'", '%path%'), + array("'<|>\" \"'\\''f'", '<|>" "\'f'), + ); + } +}