From be9cb92a41c89dcb4d1e006ee6d369a6699ca86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Mon, 8 Jun 2020 21:53:13 +0200 Subject: [PATCH 1/8] Reset question validator attempts only for actual stdin --- Helper/QuestionHelper.php | 8 +++--- Tests/Helper/QuestionHelperTest.php | 39 +++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index 134f6231e..f9d74ebe5 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -509,14 +509,16 @@ private function getShell() private function isTty(): bool { - $inputStream = !$this->inputStream && \defined('STDIN') ? STDIN : $this->inputStream; + if (!\defined('STDIN')) { + return true; + } if (\function_exists('stream_isatty')) { - return stream_isatty($inputStream); + return stream_isatty(fopen('php://input', 'r')); } if (\function_exists('posix_isatty')) { - return posix_isatty($inputStream); + return posix_isatty(fopen('php://input', 'r')); } return true; diff --git a/Tests/Helper/QuestionHelperTest.php b/Tests/Helper/QuestionHelperTest.php index ddb0c90e1..b9eb2f66d 100644 --- a/Tests/Helper/QuestionHelperTest.php +++ b/Tests/Helper/QuestionHelperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\Tests\Helper; +use Symfony\Component\Console\Application; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\FormatterHelper; @@ -21,6 +22,7 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Terminal; +use Symfony\Component\Console\Tester\ApplicationTester; /** * @group tty @@ -727,21 +729,36 @@ public function testAskThrowsExceptionOnMissingInputWithValidator() $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), $question); } - public function testAskThrowsExceptionFromValidatorEarlyWhenTtyIsMissing() + public function testQuestionValidatorRepeatsThePrompt() { - $this->expectException('Exception'); - $this->expectExceptionMessage('Bar, not Foo'); + $tries = 0; + $application = new Application(); + $application->setAutoExit(false); + $application->register('question') + ->setCode(function ($input, $output) use (&$tries) { + $question = new Question('This is a promptable question'); + $question->setValidator(function ($value) use (&$tries) { + ++$tries; + if (!$value) { + throw new \Exception(); + } - $output = $this->getMockBuilder('\Symfony\Component\Console\Output\OutputInterface')->getMock(); - $output->expects($this->once())->method('writeln'); + return $value; + }); + + (new QuestionHelper())->ask($input, $output, $question); - (new QuestionHelper())->ask( - $this->createStreamableInputInterfaceMock($this->getInputStream('Foo'), true), - $output, - (new Question('Q?'))->setHidden(true)->setValidator(function ($input) { - throw new \Exception("Bar, not $input"); + return 0; }) - ); + ; + + $tester = new ApplicationTester($application); + $tester->setInputs(['', 'not-empty']); + + $statusCode = $tester->run(['command' => 'question'], ['interactive' => true]); + + $this->assertSame(2, $tries); + $this->assertSame($statusCode, 0); } public function testEmptyChoices() From a3660e1c74be6d5a02b884f57e80a4e440a705ef Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 15 Jun 2020 14:27:36 +0200 Subject: [PATCH 2/8] [Console] Reset question validator attempts only for actual stdin (bis) --- Helper/QuestionHelper.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index f9d74ebe5..1b60786c9 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -104,7 +104,7 @@ private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); - $inputStream = $this->inputStream ?: STDIN; + $inputStream = $this->inputStream ?: fopen('php://stdin', 'r'); $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { @@ -474,7 +474,7 @@ private function validateAttempts(callable $interviewer, OutputInterface $output } catch (\Exception $error) { } - $attempts = $attempts ?? -(int) $this->isTty(); + $attempts = $attempts ?? -(int) $this->askForever(); } throw $error; @@ -507,18 +507,20 @@ private function getShell() return self::$shell; } - private function isTty(): bool + private function askForever(): bool { - if (!\defined('STDIN')) { + $inputStream = $this->inputStream ?: fopen('php://stdin', 'r'); + + if ('php://stdin' !== (stream_get_meta_data($inputStream)['url'] ?? null)) { return true; } if (\function_exists('stream_isatty')) { - return stream_isatty(fopen('php://input', 'r')); + return stream_isatty($inputStream); } if (\function_exists('posix_isatty')) { - return posix_isatty(fopen('php://input', 'r')); + return posix_isatty($inputStream); } return true; From b4adbbd0b8bdc11363015789be9911ba38d6f02a Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Tue, 23 Jun 2020 14:31:34 +0200 Subject: [PATCH 3/8] Fixed typo in test name --- Tests/Helper/QuestionHelperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Helper/QuestionHelperTest.php b/Tests/Helper/QuestionHelperTest.php index 93b762c26..f1a7676a0 100644 --- a/Tests/Helper/QuestionHelperTest.php +++ b/Tests/Helper/QuestionHelperTest.php @@ -1014,7 +1014,7 @@ public function testTraversableAutocomplete() $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } - public function testDisableSttby() + public function testDisableStty() { if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); From cbbaa74b947a76fdc5ef0fc4dff17f22c10e1ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 30 Jun 2020 14:50:28 +0200 Subject: [PATCH 4/8] Removed comments and requirements relative to php <5.5 (not supported anymore) --- Command/Command.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Command/Command.php b/Command/Command.php index 311fdb6a1..e39cc3ba4 100644 --- a/Command/Command.php +++ b/Command/Command.php @@ -437,8 +437,6 @@ public function setName($name) * This feature should be used only when creating a long process command, * like a daemon. * - * PHP 5.5+ or the proctitle PECL library is required - * * @param string $title The process title * * @return $this From fc323759f84afa467e0f34d88e57e40cd23dc9ac Mon Sep 17 00:00:00 2001 From: YaFou <33806646+YaFou@users.noreply.github.com> Date: Wed, 1 Jul 2020 09:43:16 +0200 Subject: [PATCH 5/8] [Console] Fixes question input encoding on Windows --- Helper/QuestionHelper.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index 80f6048b8..ee6d4b664 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -158,6 +158,11 @@ private function doAsk(OutputInterface $output, Question $question) $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterValues(); + if (\function_exists('sapi_windows_cp_set')) { + // Codepage used by cmd.exe on Windows to allow special characters (éàüñ). + sapi_windows_cp_set(1252); + } + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { From 426b401f5504b4e52b4b3fd9f133c2cbf412b378 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 1 Jul 2020 14:09:20 +0200 Subject: [PATCH 6/8] [Console] always use stty when possible to ask hidden questions --- Helper/QuestionHelper.php | 83 +++++++++++++-------------------------- 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index 1b60786c9..6efec5b5b 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -34,6 +34,7 @@ class QuestionHelper extends Helper private $inputStream; private static $shell; private static $stty = true; + private static $stdinIsInteractive; /** * Asks a question to the user. @@ -419,33 +420,26 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $ if (self::$stty && Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); - shell_exec('stty -echo'); - $value = fgets($inputStream, 4096); - shell_exec(sprintf('stty %s', $sttyMode)); + } elseif ($this->isInteractiveInput($inputStream)) { + throw new RuntimeException('Unable to hide the response.'); + } - if (false === $value) { - throw new MissingInputException('Aborted.'); - } - if ($trimmable) { - $value = trim($value); - } - $output->writeln(''); + $value = fgets($inputStream, 4096); - return $value; + if (self::$stty && Terminal::hasSttyAvailable()) { + shell_exec(sprintf('stty %s', $sttyMode)); } - if (false !== $shell = $this->getShell()) { - $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; - $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword' 2> /dev/null", $shell, $readCmd); - $sCommand = shell_exec($command); - $value = $trimmable ? rtrim($sCommand) : $sCommand; - $output->writeln(''); - - return $value; + if (false === $value) { + throw new MissingInputException('Aborted.'); + } + if ($trimmable) { + $value = trim($value); } + $output->writeln(''); - throw new RuntimeException('Unable to hide the response.'); + return $value; } /** @@ -473,56 +467,35 @@ private function validateAttempts(callable $interviewer, OutputInterface $output throw $e; } catch (\Exception $error) { } - - $attempts = $attempts ?? -(int) $this->askForever(); } throw $error; } - /** - * Returns a valid unix shell. - * - * @return string|bool The valid shell name, false in case no valid shell is found - */ - private function getShell() + private function isInteractiveInput($inputStream): bool { - if (null !== self::$shell) { - return self::$shell; + if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) { + return false; } - self::$shell = false; - - if (file_exists('/usr/bin/env')) { - // handle other OSs with bash/zsh/ksh/csh if available to hide the answer - $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; - foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { - if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { - self::$shell = $sh; - break; - } - } - } - - return self::$shell; - } - - private function askForever(): bool - { - $inputStream = $this->inputStream ?: fopen('php://stdin', 'r'); - - if ('php://stdin' !== (stream_get_meta_data($inputStream)['url'] ?? null)) { - return true; + if (null !== self::$stdinIsInteractive) { + return self::$stdinIsInteractive; } if (\function_exists('stream_isatty')) { - return stream_isatty($inputStream); + return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r')); } if (\function_exists('posix_isatty')) { - return posix_isatty($inputStream); + return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r')); } - return true; + if (!\function_exists('exec')) { + return self::$stdinIsInteractive = true; + } + + exec('stty 2> /dev/null', $output, $status); + + return self::$stdinIsInteractive = 1 !== $status; } } From 5e980f99034c73c00b06959345728dc0dc29d512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Wed, 1 Jul 2020 12:07:23 +0200 Subject: [PATCH 7/8] [Console] Do not check for "stty" using "exec" if that function is disabled --- Terminal.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Terminal.php b/Terminal.php index 43a318262..774c5f76b 100644 --- a/Terminal.php +++ b/Terminal.php @@ -66,6 +66,11 @@ public static function hasSttyAvailable() return self::$stty; } + // skip check if exec function is disabled + if (!\function_exists('exec')) { + return false; + } + exec('stty 2>&1', $output, $exitcode); return self::$stty = 0 === $exitcode; From 9f9ab1ef18b733b948783c43e4f40ea70c5d8d95 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 4 Jul 2020 11:30:27 +0200 Subject: [PATCH 8/8] [Console] fix reading from STDIN --- Helper/QuestionHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index 6efec5b5b..72c270e22 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -105,7 +105,7 @@ private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); - $inputStream = $this->inputStream ?: fopen('php://stdin', 'r'); + $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {