bug #37469 [Console] always use stty when possible to ask hidden questions (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[Console] always use stty when possible to ask hidden questions

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #36565, replaces #36590
| License       | MIT
| Doc PR        | -

The current code doesn't make much sense: we check `hasSttyAvailable()`, and if the answer is `false`, we still use `stty` directly.

This PR relies on `stream_isatty` and equivalent fallback checks to decide if the password can be hidden or not.

Best reviewed [ignoring whitespaces](https://github.com/symfony/symfony/pull/37469/files?w=1).

Commits
-------

055b605e30 [Console] always use stty when possible to ask hidden questions
This commit is contained in:
Nicolas Grekas 2020-07-03 20:04:21 +02:00
commit fec23313e7
1 changed files with 29 additions and 56 deletions

View File

@ -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 @@ class QuestionHelper extends Helper
if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
} elseif ($this->isInteractiveInput($inputStream)) {
throw new RuntimeException('Unable to hide the response.');
}
$value = fgets($inputStream, 4096);
if (self::$stty && Terminal::hasSttyAvailable()) {
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new MissingInputException('Aborted.');
}
if ($trimmable) {
$value = trim($value);
}
$output->writeln('');
return $value;
}
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 @@ class QuestionHelper extends Helper
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;
}
}