Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3db4904

Browse files
[Process] Strong args escaping on Windows + deprecate compat settings
1 parent f8b02ed commit 3db4904

12 files changed

+219
-84
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ script:
9696
- if [[ ! $deps && ! $PHP = hhvm* ]]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'"$REPORT"; fi
9797
- if [[ ! $deps && ! $PHP = hhvm* ]]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi
9898
- if [[ ! $deps && $PHP = hhvm* ]]; then $PHPUNIT --exclude-group benchmark,intl-data; fi
99-
- if [[ ! $deps && $PHP = ${MIN_PHP%.*} ]]; then echo -e "1\\n0" | xargs -I{} sh -c 'echo "\\nPHP --enable-sigchild enhanced={}" && ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/'; fi
99+
- if [[ ! $deps && $PHP = ${MIN_PHP%.*} ]]; then echo -e "1\\n0" | xargs -I{} sh -c 'echo "\\nPHP --enable-sigchild enhanced={}" && SYMFONY_DEPRECATIONS_HELPER=weak ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/'; fi
100100
- if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY"$REPORT"; fi
101101
- if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi --prefer-lowest --prefer-stable; $PHPUNIT --exclude-group tty,benchmark,intl-data'"$REPORT"; fi
102102
# Test the PhpUnit bridge using the original phpunit script

UPGRADE-3.3.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ HttpKernel
4242
* The `Psr6CacheClearer::addPool()` method has been deprecated. Pass an array of pools indexed
4343
by name to the constructor instead.
4444

45+
Process
46+
-------
47+
48+
* On Windows, `!VAR!` expansion inside escaped arguments is deprecated.
49+
50+
* Not inheriting environment variables is deprecated.
51+
52+
* Configuring `proc_open()` options is deprecated.
53+
54+
* Configuring Windows and sigchild compatibility is deprecated - they will be always enabled in 4.0.
55+
4556
Security
4657
--------
4758

UPGRADE-4.0.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,17 @@ HttpKernel
204204
* The `Psr6CacheClearer::addPool()` method has been removed. Pass an array of pools indexed
205205
by name to the constructor instead.
206206

207+
Process
208+
-------
209+
210+
* On Windows, `!VAR!` variables are not expanded anymore in escaped arguments.
211+
212+
* Environment variables are always inherited in sub-processes.
213+
214+
* Configuring `proc_open()` options has been removed.
215+
216+
* Configuring Windows and sigchild compatibility is not possible anymore - they are always enabled.
217+
207218
Security
208219
--------
209220

src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Console\Output\StreamOutput;
1717
use Symfony\Component\Console\Helper\ProcessHelper;
1818
use Symfony\Component\Process\Process;
19+
use Symfony\Component\Process\ProcessUtils;
1920

2021
class ProcessHelperTest extends \PHPUnit_Framework_TestCase
2122
{
@@ -84,7 +85,9 @@ public function provideCommandsAndOutput()
8485

8586
$errorMessage = 'An error occurred';
8687
if ('\\' === DIRECTORY_SEPARATOR) {
87-
$successOutputProcessDebug = str_replace("'", '"', $successOutputProcessDebug);
88+
$args = array('php', '-r', 'echo 42;');
89+
$args = array_map(array(ProcessUtils::class, 'escapeArgument'), $args);
90+
$successOutputProcessDebug = str_replace("'php' '-r' 'echo 42;'", implode(' ', $args), $successOutputProcessDebug);
8891
}
8992

9093
return array(

src/Symfony/Component/Process/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
CHANGELOG
22
=========
33

4+
3.3.0
5+
-----
6+
7+
* deprecated `!VAR!` expansion inside escaped arguments
8+
* deprecated not inheriting environment variables
9+
* deprecated configuring `proc_open()` options
10+
* deprecated configuring enhanced Windows compatibility
11+
* deprecated configuring enhanced sigchild compatibility
12+
413
2.5.0
514
-----
615

src/Symfony/Component/Process/PhpProcess.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class PhpProcess extends Process
3333
* @param int $timeout The timeout in seconds
3434
* @param array $options An array of options for proc_open
3535
*/
36-
public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = array())
36+
public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = null)
3737
{
3838
$executableFinder = new PhpExecutableFinder();
3939
if (false === $php = $executableFinder->find()) {
@@ -52,6 +52,9 @@ public function __construct($script, $cwd = null, array $env = null, $timeout =
5252
// command with exec
5353
$php = 'exec '.$php;
5454
}
55+
if (null !== $options) {
56+
@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);
57+
}
5558

5659
parent::__construct($php, $cwd, $env, $script, $timeout, $options);
5760
}

src/Symfony/Component/Process/Process.php

Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class Process implements \IteratorAggregate
5858
private $lastOutputTime;
5959
private $timeout;
6060
private $idleTimeout;
61-
private $options;
61+
private $options = array('suppress_errors' => true);
6262
private $exitcode;
6363
private $fallbackStatus = array();
6464
private $processInformation;
@@ -145,7 +145,7 @@ class Process implements \IteratorAggregate
145145
*
146146
* @throws RuntimeException When proc_open is not installed
147147
*/
148-
public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
148+
public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
149149
{
150150
if (!function_exists('proc_open')) {
151151
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
171171
$this->pty = false;
172172
$this->enhanceWindowsCompatibility = true;
173173
$this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
174-
$this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
174+
if (null !== $options) {
175+
@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);
176+
$this->options = array_replace($this->options, $options);
177+
}
175178
}
176179

177180
public function __destruct()
@@ -268,47 +271,40 @@ public function start(callable $callback = null)
268271
$descriptors = $this->getDescriptors();
269272

270273
$commandline = $this->commandline;
271-
$envline = '';
272274

273-
if (null !== $this->env && $this->inheritEnv) {
274-
if ('\\' === DIRECTORY_SEPARATOR && !empty($this->options['bypass_shell']) && !$this->enhanceWindowsCompatibility) {
275-
throw new LogicException('The "bypass_shell" option must be false to inherit environment variables while enhanced Windows compatibility is off');
276-
}
277-
$env = '\\' === DIRECTORY_SEPARATOR ? '(SET %s)&&' : 'export %s;';
278-
foreach ($this->env as $k => $v) {
279-
$envline .= sprintf($env, ProcessUtils::escapeArgument("$k=$v"));
275+
$env = $this->env;
276+
$envBackup = array();
277+
if (null !== $env && $this->inheritEnv) {
278+
foreach ($env as $k => $v) {
279+
$envBackup[$k] = getenv($v);
280+
putenv(false === $v ? $k : "$k=$v");
280281
}
281282
$env = null;
282-
} else {
283-
$env = $this->env;
283+
} elseif (null !== $env) {
284+
@trigger_error(sprintf('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', __METHOD__), E_USER_DEPRECATED);
284285
}
285286
if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
286-
$commandline = 'cmd /V:ON /E:ON /D /C "('.$envline.$commandline.')';
287-
foreach ($this->processPipes->getFiles() as $offset => $filename) {
288-
$commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename);
289-
}
290-
$commandline .= '"';
291-
292-
if (!isset($this->options['bypass_shell'])) {
293-
$this->options['bypass_shell'] = true;
294-
}
287+
$this->options['bypass_shell'] = true;
288+
$commandline = $this->prepareWindowsCommandLine($commandline, $envBackup);
295289
} elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
296290
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
297291
$descriptors[3] = array('pipe', 'w');
298292

299293
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
300-
$commandline = $envline.'{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
294+
$commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
301295
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
302296

303297
// Workaround for the bug, when PTS functionality is enabled.
304298
// @see : https://bugs.php.net/69442
305299
$ptsWorkaround = fopen(__FILE__, 'r');
306-
} elseif ('' !== $envline) {
307-
$commandline = $envline.$commandline;
308300
}
309301

310302
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $env, $this->options);
311303

304+
foreach ($envBackup as $k => $v) {
305+
putenv(false === $v ? $k : "$k=$v");
306+
}
307+
312308
if (!is_resource($this->process)) {
313309
throw new RuntimeException('Unable to launch a new process.');
314310
}
@@ -1089,6 +1085,7 @@ public function getEnv()
10891085
*
10901086
* An environment variable value should be a string.
10911087
* If it is an array, the variable is ignored.
1088+
* If it is false, it will be removed when env vars are otherwise inherited.
10921089
*
10931090
* That happens in PHP when 'argv' is registered into
10941091
* the $_ENV array for instance.
@@ -1106,7 +1103,7 @@ public function setEnv(array $env)
11061103

11071104
$this->env = array();
11081105
foreach ($env as $key => $value) {
1109-
$this->env[$key] = (string) $value;
1106+
$this->env[$key] = $value;
11101107
}
11111108

11121109
return $this;
@@ -1148,9 +1145,13 @@ public function setInput($input)
11481145
* Gets the options for proc_open.
11491146
*
11501147
* @return array The current options
1148+
*
1149+
* @deprecated since version 3.3, to be removed in 4.0.
11511150
*/
11521151
public function getOptions()
11531152
{
1153+
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
1154+
11541155
return $this->options;
11551156
}
11561157

@@ -1160,9 +1161,13 @@ public function getOptions()
11601161
* @param array $options The new options
11611162
*
11621163
* @return self The current Process instance
1164+
*
1165+
* @deprecated since version 3.3, to be removed in 4.0.
11631166
*/
11641167
public function setOptions(array $options)
11651168
{
1169+
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
1170+
11661171
$this->options = $options;
11671172

11681173
return $this;
@@ -1174,9 +1179,13 @@ public function setOptions(array $options)
11741179
* This is true by default.
11751180
*
11761181
* @return bool
1182+
*
1183+
* @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
11771184
*/
11781185
public function getEnhanceWindowsCompatibility()
11791186
{
1187+
@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);
1188+
11801189
return $this->enhanceWindowsCompatibility;
11811190
}
11821191

@@ -1186,9 +1195,13 @@ public function getEnhanceWindowsCompatibility()
11861195
* @param bool $enhance
11871196
*
11881197
* @return self The current Process instance
1198+
*
1199+
* @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
11891200
*/
11901201
public function setEnhanceWindowsCompatibility($enhance)
11911202
{
1203+
@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);
1204+
11921205
$this->enhanceWindowsCompatibility = (bool) $enhance;
11931206

11941207
return $this;
@@ -1198,9 +1211,13 @@ public function setEnhanceWindowsCompatibility($enhance)
11981211
* Returns whether sigchild compatibility mode is activated or not.
11991212
*
12001213
* @return bool
1214+
*
1215+
* @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
12011216
*/
12021217
public function getEnhanceSigchildCompatibility()
12031218
{
1219+
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED);
1220+
12041221
return $this->enhanceSigchildCompatibility;
12051222
}
12061223

@@ -1214,9 +1231,13 @@ public function getEnhanceSigchildCompatibility()
12141231
* @param bool $enhance
12151232
*
12161233
* @return self The current Process instance
1234+
*
1235+
* @deprecated since version 3.3, to be removed in 4.0.
12171236
*/
12181237
public function setEnhanceSigchildCompatibility($enhance)
12191238
{
1239+
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED);
1240+
12201241
$this->enhanceSigchildCompatibility = (bool) $enhance;
12211242

12221243
return $this;
@@ -1231,6 +1252,10 @@ public function setEnhanceSigchildCompatibility($enhance)
12311252
*/
12321253
public function inheritEnvironmentVariables($inheritEnv = true)
12331254
{
1255+
if (!$inheritEnv) {
1256+
@trigger_error(sprintf('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', __METHOD__), E_USER_DEPRECATED);
1257+
}
1258+
12341259
$this->inheritEnv = (bool) $inheritEnv;
12351260

12361261
return $this;
@@ -1240,9 +1265,13 @@ public function inheritEnvironmentVariables($inheritEnv = true)
12401265
* Returns whether environment variables will be inherited or not.
12411266
*
12421267
* @return bool
1268+
*
1269+
* @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
12431270
*/
12441271
public function areEnvironmentVariablesInherited()
12451272
{
1273+
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), E_USER_DEPRECATED);
1274+
12461275
return $this->inheritEnv;
12471276
}
12481277

@@ -1561,6 +1590,50 @@ private function doSignal($signal, $throwException)
15611590
return true;
15621591
}
15631592

1593+
private function prepareWindowsCommandLine($cmd, array &$envBackup)
1594+
{
1595+
$uid = uniqid('', true);
1596+
$varCount = 0;
1597+
$varCache = array();
1598+
$cmd = preg_replace_callback(
1599+
'/"(
1600+
[^"%!]*+
1601+
(?:
1602+
(?: !LF! | "(?:\^[%!])?+" )
1603+
[^"%!]*+
1604+
)++
1605+
)"/x',
1606+
function ($m) use (&$envBackup, &$varCache, &$varCount, $uid) {
1607+
if (isset($varCache[$m[0]])) {
1608+
return $varCache[$m[0]];
1609+
}
1610+
if (false !== strpos($value = $m[1], "\0")) {
1611+
$value = str_replace("\0", '?', $value);
1612+
}
1613+
if (false === strpbrk($value, "\"%!\n")) {
1614+
return '"'.$value.'"';
1615+
}
1616+
1617+
$value = str_replace(array('!LF!', '"^!"', '"^%"', '""'), array("\n", '!', '%', '"'), $value);
1618+
$value = preg_replace('/(\\\\*)"/', '$1$1\\"', $value);
1619+
1620+
$var = $uid.++$varCount;
1621+
putenv("$var=\"$value\"");
1622+
$envBackup[$var] = false;
1623+
1624+
return $varCache[$m[0]] = '!'.$var.'!';
1625+
},
1626+
$cmd
1627+
);
1628+
1629+
$cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
1630+
foreach ($this->processPipes->getFiles() as $offset => $filename) {
1631+
$cmd .= ' '.$offset.'>"'.$filename.'"';
1632+
}
1633+
1634+
return $cmd;
1635+
}
1636+
15641637
/**
15651638
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
15661639
*

0 commit comments

Comments
 (0)