Skip to content

Commit

Permalink
bug #30277 [Console] Prevent ArgvInput::getFirstArgument() from retur…
Browse files Browse the repository at this point in the history
…ning an option value (chalasr)

This PR was merged into the 3.4 branch.

Discussion
----------

[Console] Prevent ArgvInput::getFirstArgument() from returning an option value

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #23343
| License       | MIT
| Doc PR        | n/a

Fixes the case where the passed input string contains no command name but one or more global (i.e. application-defined) options accepting values.

Commits
-------

46461e9 [Console] Prevent ArgvInput::getFirstArgument() from returning an option value
  • Loading branch information
fabpot committed Feb 21, 2019
2 parents 68d5597 + 46461e9 commit ff4b1d4
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 2 deletions.
7 changes: 7 additions & 0 deletions src/Symfony/Component/Console/Application.php
Expand Up @@ -202,6 +202,13 @@ public function doRun(InputInterface $input, OutputInterface $output)
return 0;
}

try {
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
// Errors must be ignored, full binding/validation happens later when the command is known.
}

$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(['--help', '-h'], true)) {
if (!$name) {
Expand Down
21 changes: 20 additions & 1 deletion src/Symfony/Component/Console/Input/ArgvInput.php
Expand Up @@ -262,8 +262,27 @@ private function addLongOption($name, $value)
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token) {
$isOption = false;
foreach ($this->tokens as $i => $token) {
if ($token && '-' === $token[0]) {
if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
continue;
}

// If it's a long option, consider that everything after "--" is the option name.
// Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
$name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
// noop
} elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
$isOption = true;
}

continue;
}

if ($isOption) {
$isOption = false;
continue;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Symfony/Component/Console/Input/InputDefinition.php
Expand Up @@ -338,8 +338,10 @@ public function getOptionDefaults()
* @return string The InputOption name
*
* @throws InvalidArgumentException When option given does not exist
*
* @internal
*/
private function shortcutToName($shortcut)
public function shortcutToName($shortcut)
{
if (!isset($this->shortcuts[$shortcut])) {
throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
Expand Down
13 changes: 13 additions & 0 deletions src/Symfony/Component/Console/Tests/ApplicationTest.php
Expand Up @@ -884,6 +884,19 @@ public function testRun()
$this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if -n is passed');
}

public function testRunWithGlobalOptionAndNoCommand()
{
$application = new Application();
$application->setAutoExit(false);
$application->setCatchExceptions(false);
$application->getDefinition()->addOption(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL));

$output = new StreamOutput(fopen('php://memory', 'w', false));
$input = new ArgvInput(['cli.php', '--foo', 'bar']);

$this->assertSame(0, $application->run($input, $output));
}

/**
* Issue #9285.
*
Expand Down
8 changes: 8 additions & 0 deletions src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php
Expand Up @@ -312,6 +312,14 @@ public function testGetFirstArgument()

$input = new ArgvInput(['cli.php', '-fbbar', 'foo']);
$this->assertEquals('foo', $input->getFirstArgument(), '->getFirstArgument() returns the first argument from the raw input');

$input = new ArgvInput(['cli.php', '--foo', 'fooval', 'bar']);
$input->bind(new InputDefinition([new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('arg')]));
$this->assertSame('bar', $input->getFirstArgument());

$input = new ArgvInput(['cli.php', '-bf', 'fooval', 'argval']);
$input->bind(new InputDefinition([new InputOption('bar', 'b', InputOption::VALUE_NONE), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputArgument('arg')]));
$this->assertSame('argval', $input->getFirstArgument());
}

public function testHasParameterOption()
Expand Down

0 comments on commit ff4b1d4

Please sign in to comment.