Skip to content

Commit 1e9e8af

Browse files
committed
bug symfony#10479 [2.3][Process] Fix escaping on Windows (romainneutron)
This PR was merged into the 2.3 branch. Discussion ---------- [2.3][Process] Fix escaping on Windows | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT Windows escaping is broken since the last merges. After digging more on Windows escaping, I realised some things: - We forbid environment variable expansion by escaping `%APPDATA%` to `^%"APPDATA"^%` - We explicitly ask for variable expansion at runtime (running the command line with the [`/V:ON`](https://github.com/symfony/symfony/blob/2.3/src/Symfony/Component/Process/Process.php#L235) flag). Running a command containing `!APPDATA!` will be escaped and expanded (our previous rule is easily overriden) - On platform that are not windows, we use strong escaping that prevents any variable expansion (`$PATH` will be escaped to `'$PATH'` that is not interpreted as the current PATH) We have three possibilities: - Keep this behavior as this. - Prefer a consistent API and use a strong escaping strategy everywhere, but it would result in a BC break (see symfony#8975). - Allow environment variable expansion and escape `%APPDATA%` to `"%APPDATA%"` Any thoughts about this ? Commits ------- 0f65f90 [Process] Fix escaping on Windows
2 parents fccf8b5 + 0f65f90 commit 1e9e8af

File tree

3 files changed

+15
-12
lines changed

3 files changed

+15
-12
lines changed

src/Symfony/Component/Process/ProcessUtils.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,18 @@ public static function escapeArgument($argument)
4747

4848
$escapedArgument = '';
4949
$quote = false;
50-
foreach (preg_split('/([%"])/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
50+
foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
5151
if ('"' === $part) {
5252
$escapedArgument .= '\\"';
53-
} elseif ('%' === $part) {
54-
$escapedArgument .= '^%';
53+
} elseif (self::isSurroundedBy($part, '%')) {
54+
// Avoid environment variable expansion
55+
$escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
5556
} else {
57+
// escape trailing backslash
5658
if ('\\' === substr($part, -1)) {
5759
$part .= '\\';
5860
}
59-
$part = escapeshellarg($part);
60-
if ('"' === $part[0] && '"' === $part[strlen($part) - 1]) {
61-
$part = substr($part, 1, -1);
62-
$quote = true;
63-
}
61+
$quote = true;
6462
$escapedArgument .= $part;
6563
}
6664
}
@@ -73,4 +71,9 @@ public static function escapeArgument($argument)
7371

7472
return escapeshellarg($argument);
7573
}
74+
75+
private static function isSurroundedBy($arg, $char)
76+
{
77+
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
78+
}
7679
}

src/Symfony/Component/Process/Tests/ProcessBuilderTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,13 @@ public function testPrefixIsPrependedToAllGeneratedProcess()
142142

143143
public function testShouldEscapeArguments()
144144
{
145-
$pb = new ProcessBuilder(array('%path%', 'foo " bar'));
145+
$pb = new ProcessBuilder(array('%path%', 'foo " bar', '%baz%baz'));
146146
$proc = $pb->getProcess();
147147

148148
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
149-
$this->assertSame('^%"path"^% "foo "\\"" bar"', $proc->getCommandLine());
149+
$this->assertSame('^%"path"^% "foo \\" bar" "%baz%baz"', $proc->getCommandLine());
150150
} else {
151-
$this->assertSame("'%path%' 'foo \" bar'", $proc->getCommandLine());
151+
$this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine());
152152
}
153153
}
154154

src/Symfony/Component/Process/Tests/ProcessUtilsTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function dataArguments()
3030
array('"\"php\" \"-v\""', '"php" "-v"'),
3131
array('"foo bar"', 'foo bar'),
3232
array('^%"path"^%', '%path%'),
33-
array('"<|>"\\"" "\\""\'f"', '<|>" "\'f'),
33+
array('"<|>\\" \\"\'f"', '<|>" "\'f'),
3434
array('""', ''),
3535
array('"with\trailingbs\\\\"', 'with\trailingbs\\'),
3636
);

0 commit comments

Comments
 (0)