-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
Conversation
|
02a6451
to
ef747f3
Compare
Maybe should you copy the full test suite too? https://github.com/johnstevenson/winbox-args/blob/master/tests/ArgsTest.php |
@johnstevenson could you please have a look at this PR, and see if the current failures+patch seems legit to you? Also, OK for adding the test suite here? |
@nicolas-grekas Will do. No worries copying the test suite |
@johnstevenson did you check whether this 'advanced' windows escaping logic should be fixed/applied to php-src? |
See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ also for reference. Looks like the escaping logic should vary if bypass_shell is on/off. |
Yes, that's what the |
ef747f3
to
b0dd4e1
Compare
a26c700
to
ba4c870
Compare
d85ebec
to
bdd686d
Compare
bdd686d
to
bca6beb
Compare
bca6beb
to
53e5652
Compare
PR green Changelog updated:
One could consider some of these changes as breaking BC. Yet, any other options/uses of the related default behaviors was putting the component in some "unknown/unpredictable" state. It couldn't work really on Windows. Eg delayed expansion what enabled as a workaround to allow using @johnstevenson sorry, I did not use your code - it did not fit here :) |
* `!VAR!` expansion allowed by ProcessUtils::escapeArgument() is deprecated. | ||
You should resolve these variables before calling `ProcessUtils::escapeArgument()`. | ||
|
||
* Configuring `proc_open()` options, Windows compatibility and SIGCHLD |
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 I
in SIGCHILD
(same in other files)
$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; |
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.
I would keep array_replace
. It makes the code more readable IMO.
@@ -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'; |
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.
wouldn't this break BC by disabling the expansion of variables due to having 2 args ?
} | ||
|
||
return escapeshellarg($argument); | ||
if ((self::ESC_WINDOWS_ARGV & $mode) && (false !== strpbrk($argument, " \t\n\v\"") || !isset($argument[0]))) { |
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.
shouldn't the case of the empty string be checked before the strpbrk
? I suspect it is a cheaper call
return escapeshellarg($argument); | ||
if ((self::ESC_WINDOWS_ARGV & $mode) && (false !== strpbrk($argument, " \t\n\v\"") || !isset($argument[0]))) { | ||
$argument = preg_replace('/(\\\\*+)"/', '$1$1\\"', $argument); | ||
$argument = preg_replace('/(\\\\++)$/', '$1$1', $argument); |
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.
for the empty string, we could skip these regexes
*/ | ||
class ProcessUtils | ||
{ | ||
const ESC_WINDOWS_ARGV = 1; | ||
const ESC_WINDOWS_CMD = 2; |
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.
we should have documentation on these to explain their respective behavior
array('^%path^%', '%path%'), | ||
array('^"^<^|^>\\^" \\^"\'f^"', '<|>" "\'f'), | ||
array('^"^"', ''), | ||
array('^"with\trailing bs\\\\^"', 'with\trailing bs\\'), |
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.
we should also have tests covering the usage of the different constants
@@ -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'; |
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.
and why applying on the CMD escaping, not the ARGV escaping, while Unix uses escapeshellarg
?
@nicolas-grekas No worries from my end for not using my code. However... The reason I did not use the code from the blog entry was because it is not reliable (incidentally, it is listed in my References section). Unfortunately, the author missed an extremely important gotcha:
from Caret Escaping Meta Characters which is part of the Implementing a solution section that ties everything together. For example On a separate point, I do not understand the bit-masked constants, which appear to make things very brittle. For example the test if (!(self::ESC_WINDOWS_ARGV_CMD & $mode)) doesn't actually restrict the
Incidentally, just before you updated the PR I was working through the appveyor test failures to check if the actual output was correct and that the test data needed changing. I am pleased to report that this is the case. Hey ho! |
Hum. In fact, the author of the MSDN article makes it clear to me:
All in all, that means there is no solution: escaping on Windows is contextual. If you don't know the context where the escaped value is going to be inserted, you can't escape it correctly... Eg there is no way to escape the Thus, I'm closing. Our enemy is parsing as done by cmd. I might come up soon again with another radically different approach based on this snippet :) putenv('LF=
');
putenv('C=SET && php -r "print_r($_SERVER);" "^^^!LF^!a!LF!b!LF!c"');
proc_close(proc_open('cmd /V:ON /E:ON /D /S /C %C%', array(), $p, null, null, array('bypass_shell' => true))); |
I don't understand what you mean by this. Of course the path-to-php.exe is parsed by |
Continued in #21347 with a radically new approach. |
Example with echo: |
Sorry, you've lost me! Just to be clear, do you believe that path-to-php.exe is not parsed by |
We don't care anymore, see #21254 :) |
Eh? This is #21254 isn't it? |
Maybe he was talking about #21347 |
I meant #21347 sorry :) |
Related to #3381.