-
Notifications
You must be signed in to change notification settings - Fork 440
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
Fix command escaping #780
Fix command escaping #780
Conversation
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.
Hello @1ed,
Thanks for the PR! This part of the tool is haunting us for some time, so I want to be carefull in 'how' we fix the issue.
That is why I added quite a lot of remarks and questions to the PR. Can you take a look at them?
@@ -60,6 +59,6 @@ private static function fixInternalComposerProcessVersion( | |||
return $process; | |||
} | |||
|
|||
return new Process(ProcessUtils::escapeArguments($commandlineArgs->getValues())); | |||
return new Process($commandlineArgs->getValues()); |
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.
Sadly, composer 1 and 2 comes shipped with a hardcoded symfony/process
v2.8 dependency:
https://github.com/composer/composer/blob/master/composer.lock#L949-L950
The development integrator runs as a composer script in a php context which contains the composer dependencies rather than with the application's dependencies.
This means that this code won't work since you now pass an array instead of a string.
We could add a method ProcessArgumentsCollection::asCommandLineString()
or something similar instead.
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.
Fixed
@@ -28,7 +28,7 @@ public function getConfigTreeBuilder(): TreeBuilder | |||
$gitHookVariables->addDefaultsIfNotSet(); | |||
$gitHookVariables->children()->scalarNode('VAGRANT_HOST_DIR')->defaultValue('.'); | |||
$gitHookVariables->children()->scalarNode('VAGRANT_PROJECT_DIR')->defaultValue('/var/www'); | |||
$gitHookVariables->children()->scalarNode('EXEC_GRUMPHP_COMMAND')->defaultValue('exec'); | |||
$gitHookVariables->children()->variableNode('EXEC_GRUMPHP_COMMAND')->defaultValue('exec'); |
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 think it is better to make it an arrayNode but to also accept string input. If it is a string input, the config is normalized into an array. (That should be possible with the symfony configuration component)
This change also needs to be added to the documentation.
spec/Process/ProcessBuilderSpec.php
Outdated
@@ -74,7 +73,7 @@ function it_outputs_the_command_when_run_very_very_verbose(IOInterface $io) | |||
$io->isVeryVerbose()->willReturn(true); | |||
|
|||
$command = '/usr/bin/grumphp'; | |||
$io->write([PHP_EOL . 'Command: ' . ProcessUtils::escapeArgument($command)], true)->shouldBeCalled(); | |||
$io->write([PHP_EOL . sprintf('Command: %1$s%2$s%1$s', '\\' === DIRECTORY_SEPARATOR ? '"' : "'", $command)], true)->shouldBeCalled(); |
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.
maybe instead of adding this directory separator logic : pass it through
Process::fromShellCommandline($command)->getCommandLine()
Instead?
Thay way it will always work the way the loaded symfony/process
wants it to be formatted?
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.
Process::fromShellCommandline
not escapes the command. I added a check if the call to $io->write
contains the command string, maybe that's enough
@@ -133,7 +133,8 @@ protected function parseHookBody(string $hook, string $templateFile): string | |||
]; | |||
|
|||
foreach ($this->hooksConfig->getVariables() as $key => $value) { | |||
$replacements[sprintf('$(%s)', $key)] = ProcessUtils::escapeArgumentsFromString($value); | |||
$process = is_array($value) ? new Process($value) : Process::fromShellCommandline($value); |
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 it be better to add a configuration normalizer that converts a string into an array?
That way we can always assume we have an array, even if the grumphp.yaml
contains a string.
I am not sure how this solves your issue though: it still escapes the configured CLI argument.
If you are on windows, it escapes with double quotes, so it might work.
However, if you are on linux: it escapes with single quotes, meaning it won't work.
Not sure if it's a good idea, but maybe we shouldn't escape the variables and leave that up to the one configurating grumphp? That way you can do something like:
grumphp:
git_hook_variables:
EXEC_GRUMPHP_COMMAND: ['docker-composer --rm --no-deps -u "$(id -u):$(id -g)"', "'php'" ]
Which will result in:
docker-composer --rm --no-deps -u "$(id -u):$(id -g)" 'php'
Meaning you can choose if you want to escape or not from the config file.
However: it means we won't escape by default anymore....
(Assuming it is a developer tool, this won't be a big security issue IMO.)
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.
In this way it's up to the developer if the command will be escaped or not. If someone uses a string it won't be escaped automatically, but if someone uses an array it will be escaped by the process component.
Won't doing automatic escaping would be the other option, but I think it's more convenient this way. It's up to the user to decide.
If it would be normalized to an array how could we decide if it needs escaping or not?
In my case I can use EXEC_GRUMPHP_COMMAND: 'docker-compose run --rm --no-deps -u $(id -u):$(id -g) php'
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.
sounds good!
@@ -95,7 +95,7 @@ protected function initializeComposer(string $path): string | |||
{ | |||
$process = new Process( | |||
[ | |||
$this->executableFinder->find('composer'), | |||
realpath($this->executableFinder->find('composer', 'composer')), |
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.
Can you tell me what the issue is exactly?
Why are you defaulting to the string 'composer' if the file cannot be found?
I try to avoid realpath as much as possible in this package, since it already caused a lot of issues.
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 have ...:vendor/bin:...
in the $PATH
so the executable finder finds that first and tries to run that with a relative path.
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.
@janvernieuwe : Does this fix the issue you had while running paratest as well? It might be related
@@ -36,16 +35,6 @@ public function format(Process $process): string | |||
return $this->formatJsonResponse($json); | |||
} | |||
|
|||
public function formatSuggestion(Process $process): string |
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.
Agree we can get rid of this method. However, we should still suggest the fix command. (See further in CR)
test/E2E/GitHookParametersTest.php
Outdated
], | ||
], | ||
]); | ||
|
||
$this->installComposer($this->rootDir); | ||
|
||
$hookPattern = '{[\'"]?'.preg_quote($php, '{').'[\'"]? [\'"]?-d[\'"]? [\'"]?date\.timezone=Europe/Brussels[\'"]?}'; | ||
$hookPattern = sprintf('{[\'"]%s[\'"] [\'"]-d date\.timezone=Europe/Brussels[\'"]}', $php); |
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.
better to keep $php
preg_quote-ed
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.
added back
@@ -102,7 +102,7 @@ public function run(ContextInterface $context): TaskResultInterface | |||
|
|||
if (!$process->isSuccessful()) { | |||
$messages = [$this->formatter->format($process)]; | |||
$fixerCommand = $this->formatter->formatSuggestion($process); | |||
$fixerCommand = $process->getCommandLine(); |
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.
Instead of creating the suggestion in the formatter, we can add arguments to the $arguments
variable as we do in the eslint task:
https://github.com/phpro/grumphp/blob/master/src/Task/ESLint.php#L92-L105
This way, we are still suggesting the fix command to the end user in case of a detected fixable item.
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.
Fixed, thanks!
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.
The code looks good and I like the way we can now configure the command! Thanks!
@@ -133,7 +133,8 @@ protected function parseHookBody(string $hook, string $templateFile): string | |||
]; | |||
|
|||
foreach ($this->hooksConfig->getVariables() as $key => $value) { | |||
$replacements[sprintf('$(%s)', $key)] = ProcessUtils::escapeArgumentsFromString($value); | |||
$process = is_array($value) ? new Process($value) : Process::fromShellCommandline($value); |
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.
sounds good!
The first commit what I need, the others are optional. I removed PhpCsFixerFormatter::formatSuggestion() which was public and not internal so I consider it as a BC break.