Skip to content

Commit fec2331

Browse files
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 ------- 055b605 [Console] always use stty when possible to ask hidden questions
2 parents ee0e37b + 055b605 commit fec2331

File tree

1 file changed

+28
-55
lines changed

1 file changed

+28
-55
lines changed

src/Symfony/Component/Console/Helper/QuestionHelper.php

+28-55
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class QuestionHelper extends Helper
3434
private $inputStream;
3535
private static $shell;
3636
private static $stty = true;
37+
private static $stdinIsInteractive;
3738

3839
/**
3940
* Asks a question to the user.
@@ -419,33 +420,26 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $
419420

420421
if (self::$stty && Terminal::hasSttyAvailable()) {
421422
$sttyMode = shell_exec('stty -g');
422-
423423
shell_exec('stty -echo');
424-
$value = fgets($inputStream, 4096);
425-
shell_exec(sprintf('stty %s', $sttyMode));
424+
} elseif ($this->isInteractiveInput($inputStream)) {
425+
throw new RuntimeException('Unable to hide the response.');
426+
}
426427

427-
if (false === $value) {
428-
throw new MissingInputException('Aborted.');
429-
}
430-
if ($trimmable) {
431-
$value = trim($value);
432-
}
433-
$output->writeln('');
428+
$value = fgets($inputStream, 4096);
434429

435-
return $value;
430+
if (self::$stty && Terminal::hasSttyAvailable()) {
431+
shell_exec(sprintf('stty %s', $sttyMode));
436432
}
437433

438-
if (false !== $shell = $this->getShell()) {
439-
$readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
440-
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword' 2> /dev/null", $shell, $readCmd);
441-
$sCommand = shell_exec($command);
442-
$value = $trimmable ? rtrim($sCommand) : $sCommand;
443-
$output->writeln('');
444-
445-
return $value;
434+
if (false === $value) {
435+
throw new MissingInputException('Aborted.');
436+
}
437+
if ($trimmable) {
438+
$value = trim($value);
446439
}
440+
$output->writeln('');
447441

448-
throw new RuntimeException('Unable to hide the response.');
442+
return $value;
449443
}
450444

451445
/**
@@ -473,56 +467,35 @@ private function validateAttempts(callable $interviewer, OutputInterface $output
473467
throw $e;
474468
} catch (\Exception $error) {
475469
}
476-
477-
$attempts = $attempts ?? -(int) $this->askForever();
478470
}
479471

480472
throw $error;
481473
}
482474

483-
/**
484-
* Returns a valid unix shell.
485-
*
486-
* @return string|bool The valid shell name, false in case no valid shell is found
487-
*/
488-
private function getShell()
475+
private function isInteractiveInput($inputStream): bool
489476
{
490-
if (null !== self::$shell) {
491-
return self::$shell;
477+
if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
478+
return false;
492479
}
493480

494-
self::$shell = false;
495-
496-
if (file_exists('/usr/bin/env')) {
497-
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
498-
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
499-
foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
500-
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
501-
self::$shell = $sh;
502-
break;
503-
}
504-
}
505-
}
506-
507-
return self::$shell;
508-
}
509-
510-
private function askForever(): bool
511-
{
512-
$inputStream = $this->inputStream ?: fopen('php://stdin', 'r');
513-
514-
if ('php://stdin' !== (stream_get_meta_data($inputStream)['url'] ?? null)) {
515-
return true;
481+
if (null !== self::$stdinIsInteractive) {
482+
return self::$stdinIsInteractive;
516483
}
517484

518485
if (\function_exists('stream_isatty')) {
519-
return stream_isatty($inputStream);
486+
return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r'));
520487
}
521488

522489
if (\function_exists('posix_isatty')) {
523-
return posix_isatty($inputStream);
490+
return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r'));
524491
}
525492

526-
return true;
493+
if (!\function_exists('exec')) {
494+
return self::$stdinIsInteractive = true;
495+
}
496+
497+
exec('stty 2> /dev/null', $output, $status);
498+
499+
return self::$stdinIsInteractive = 1 !== $status;
527500
}
528501
}

0 commit comments

Comments
 (0)