-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Process] Windows: stronger escaping, disabled delayed expansion, deprecated options #21254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…recated options
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,7 @@ class Process implements \IteratorAggregate | |
| private $lastOutputTime; | ||
| private $timeout; | ||
| private $idleTimeout; | ||
| private $options; | ||
| private $options = array('suppress_errors' => true); | ||
| private $exitcode; | ||
| private $fallbackStatus = array(); | ||
| private $processInformation; | ||
|
|
@@ -145,7 +145,7 @@ class Process implements \IteratorAggregate | |
| * | ||
| * @throws RuntimeException When proc_open is not installed | ||
| */ | ||
| public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) | ||
| public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null) | ||
| { | ||
| if (!function_exists('proc_open')) { | ||
| throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); | ||
|
|
@@ -171,7 +171,10 @@ public function __construct($commandline, $cwd = null, array $env = null, $input | |
| $this->pty = false; | ||
| $this->enhanceWindowsCompatibility = true; | ||
| $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); | ||
| $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options); | ||
| if (null !== $options) { | ||
| @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); | ||
| $this->options = $options + $this->options; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would keep |
||
| } | ||
| } | ||
|
|
||
| public function __destruct() | ||
|
|
@@ -274,24 +277,23 @@ public function start(callable $callback = null) | |
| if ('\\' === DIRECTORY_SEPARATOR && !empty($this->options['bypass_shell']) && !$this->enhanceWindowsCompatibility) { | ||
| throw new LogicException('The "bypass_shell" option must be false to inherit environment variables while enhanced Windows compatibility is off'); | ||
| } | ||
| $escape = '\\' === DIRECTORY_SEPARATOR ? function ($v) { return ProcessUtils::escapeArgument($v, ProcessUtils::ESC_WINDOWS_CMD); } : 'escapeshellarg'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wouldn't this break BC by disabling the expansion of variables due to having 2 args ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and why applying on the CMD escaping, not the ARGV escaping, while Unix uses |
||
| $env = '\\' === DIRECTORY_SEPARATOR ? '(SET %s)&&' : 'export %s;'; | ||
| foreach ($this->env as $k => $v) { | ||
| $envline .= sprintf($env, ProcessUtils::escapeArgument("$k=$v")); | ||
| $envline .= sprintf($env, $escape("$k=$v")); | ||
| } | ||
| $env = null; | ||
| } else { | ||
| $env = $this->env; | ||
| } | ||
| if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { | ||
| $commandline = 'cmd /V:ON /E:ON /D /C "('.$envline.$commandline.')'; | ||
| $commandline = 'cmd /V:OFF /E:ON /D /C "('.$envline.$commandline.')'; | ||
| foreach ($this->processPipes->getFiles() as $offset => $filename) { | ||
| $commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename); | ||
| $commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename, ProcessUtils::ESC_WINDOWS_CMD); | ||
| } | ||
| $commandline .= '"'; | ||
|
|
||
| if (!isset($this->options['bypass_shell'])) { | ||
| $this->options['bypass_shell'] = true; | ||
| } | ||
| $this->options['bypass_shell'] = true; | ||
| } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { | ||
| // last exit code is output on the fourth pipe and caught to work around --enable-sigchild | ||
| $descriptors[3] = array('pipe', 'w'); | ||
|
|
@@ -1148,9 +1150,13 @@ public function setInput($input) | |
| * Gets the options for proc_open. | ||
| * | ||
| * @return array The current options | ||
| * | ||
| * @deprecated since version 3.3, to be removed in 4.0. | ||
| */ | ||
| public function getOptions() | ||
| { | ||
| @trigger_error(sprintf('The %s method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); | ||
|
|
||
| return $this->options; | ||
| } | ||
|
|
||
|
|
@@ -1160,9 +1166,13 @@ public function getOptions() | |
| * @param array $options The new options | ||
| * | ||
| * @return self The current Process instance | ||
| * | ||
| * @deprecated since version 3.3, to be removed in 4.0. | ||
| */ | ||
| public function setOptions(array $options) | ||
| { | ||
| @trigger_error(sprintf('The %s method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); | ||
|
|
||
| $this->options = $options; | ||
|
|
||
| return $this; | ||
|
|
@@ -1174,9 +1184,13 @@ public function setOptions(array $options) | |
| * This is true by default. | ||
| * | ||
| * @return bool | ||
| * | ||
| * @deprecated since version 3.3, to be removed in 4.0. | ||
| */ | ||
| public function getEnhanceWindowsCompatibility() | ||
| { | ||
| @trigger_error(sprintf('The %s method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); | ||
|
|
||
| return $this->enhanceWindowsCompatibility; | ||
| } | ||
|
|
||
|
|
@@ -1186,9 +1200,13 @@ public function getEnhanceWindowsCompatibility() | |
| * @param bool $enhance | ||
| * | ||
| * @return self The current Process instance | ||
| * | ||
| * @deprecated since version 3.3, to be removed in 4.0. | ||
| */ | ||
| public function setEnhanceWindowsCompatibility($enhance) | ||
| { | ||
| @trigger_error(sprintf('The %s method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); | ||
|
|
||
| $this->enhanceWindowsCompatibility = (bool) $enhance; | ||
|
|
||
| return $this; | ||
|
|
@@ -1198,9 +1216,13 @@ public function setEnhanceWindowsCompatibility($enhance) | |
| * Returns whether sigchild compatibility mode is activated or not. | ||
| * | ||
| * @return bool | ||
| * | ||
| * @deprecated since version 3.3, to be removed in 4.0. | ||
| */ | ||
| public function getEnhanceSigchildCompatibility() | ||
| { | ||
| @trigger_error(sprintf('The %s method is deprecated since version 3.3 and will be removed in 4.0. SIGCHLD compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); | ||
|
|
||
| return $this->enhanceSigchildCompatibility; | ||
| } | ||
|
|
||
|
|
@@ -1214,9 +1236,13 @@ public function getEnhanceSigchildCompatibility() | |
| * @param bool $enhance | ||
| * | ||
| * @return self The current Process instance | ||
| * | ||
| * @deprecated since version 3.3, to be removed in 4.0. | ||
| */ | ||
| public function setEnhanceSigchildCompatibility($enhance) | ||
| { | ||
| @trigger_error(sprintf('The %s method is deprecated since version 3.3 and will be removed in 4.0. SIGCHLD compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); | ||
|
|
||
| $this->enhanceSigchildCompatibility = (bool) $enhance; | ||
|
|
||
| return $this; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,11 +17,13 @@ | |
| * ProcessUtils is a bunch of utility methods. | ||
| * | ||
| * This class contains static methods only and is not meant to be instantiated. | ||
| * | ||
| * @author Martin Hasoň <martin.hason@gmail.com> | ||
| */ | ||
| class ProcessUtils | ||
| { | ||
| const ESC_WINDOWS_ARGV = 1; | ||
| const ESC_WINDOWS_CMD = 2; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should have documentation on these to explain their respective behavior |
||
| const ESC_WINDOWS_ARGV_CMD = 3; | ||
|
|
||
| /** | ||
| * This class should not be instantiated. | ||
| */ | ||
|
|
@@ -32,46 +34,42 @@ private function __construct() | |
| /** | ||
| * Escapes a string to be used as a shell argument. | ||
| * | ||
| * Provides a more robust method on Windows than escapeshellarg. | ||
| * See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ | ||
| * | ||
| * @param string $argument The argument that will be escaped | ||
| * @param int $mode A bitfield of self::ESC_WINDOWS_* constants to configure escaping context on Windows | ||
| * | ||
| * @return string The escaped argument | ||
| */ | ||
| public static function escapeArgument($argument) | ||
| public static function escapeArgument($argument, $mode = self::ESC_WINDOWS_ARGV_CMD) | ||
| { | ||
| //Fix for PHP bug #43784 escapeshellarg removes % from given string | ||
| //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows | ||
| //@see https://bugs.php.net/bug.php?id=43784 | ||
| //@see https://bugs.php.net/bug.php?id=49446 | ||
| if ('\\' === DIRECTORY_SEPARATOR) { | ||
| if ('' === $argument) { | ||
| return escapeshellarg($argument); | ||
| } | ||
| if ('\\' !== DIRECTORY_SEPARATOR) { | ||
| return escapeshellarg($argument); | ||
| } | ||
| if (!(self::ESC_WINDOWS_ARGV_CMD & $mode)) { | ||
| throw new \InvalidArgumentException(sprintf('The $mode argument of %s must be a non-zero bitfield of self::ESC_WINDOWS_* constants.', __METHOD__)); | ||
| } | ||
|
|
||
| $escapedArgument = ''; | ||
| $quote = false; | ||
| foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { | ||
| if ('"' === $part) { | ||
| $escapedArgument .= '\\"'; | ||
| } elseif (self::isSurroundedBy($part, '%')) { | ||
| // Avoid environment variable expansion | ||
| $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%'; | ||
| } else { | ||
| // escape trailing backslash | ||
| if ('\\' === substr($part, -1)) { | ||
| $part .= '\\'; | ||
| } | ||
| $quote = true; | ||
| $escapedArgument .= $part; | ||
| } | ||
| } | ||
| if ($quote) { | ||
| $escapedArgument = '"'.$escapedArgument.'"'; | ||
| } | ||
| if (1 === func_num_args()) { | ||
| $argument = preg_replace_callback("/!([^!=\n]++)!/", function ($m) { | ||
| @trigger_error(sprintf('Delayed variables are deprecated since Symfony 3.3 and will be left unresolved in 4.0. Resolve the %s variable before calling ProcessUtil::escapeArgument().', $m[0]), E_USER_DEPRECATED); | ||
|
|
||
| return $escapedArgument; | ||
| return getenv($m[1]); | ||
| }, $argument); | ||
| } | ||
|
|
||
| return escapeshellarg($argument); | ||
| if ((self::ESC_WINDOWS_ARGV & $mode) && (false !== strpbrk($argument, " \t\n\v\"") || !isset($argument[0]))) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't the case of the empty string be checked before the |
||
| $argument = preg_replace('/(\\\\*+)"/', '$1$1\\"', $argument); | ||
| $argument = preg_replace('/(\\\\++)$/', '$1$1', $argument); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for the empty string, we could skip these regexes |
||
| $argument = '"'.$argument.'"'; | ||
| } | ||
|
|
||
| if (self::ESC_WINDOWS_CMD & $mode) { | ||
| $argument = preg_replace('/[()%!^"<>&|]/', '^$0', $argument); | ||
| } | ||
|
|
||
| return $argument; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -111,9 +109,4 @@ public static function validateInput($caller, $input) | |
|
|
||
| return $input; | ||
| } | ||
|
|
||
| private static function isSurroundedBy($arg, $char) | ||
| { | ||
| return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing
IinSIGCHILD(same in other files)