CliPrompt.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. /*
  3. * This file is part of CLI Prompt.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Seld\CliPrompt;
  11. class CliPrompt
  12. {
  13. /**
  14. * Prompts the user for input and shows what they type
  15. *
  16. * @return string
  17. */
  18. public static function prompt()
  19. {
  20. $stdin = fopen('php://stdin', 'r');
  21. $answer = self::trimAnswer(fgets($stdin, 4096));
  22. fclose($stdin);
  23. return $answer;
  24. }
  25. /**
  26. * Prompts the user for input and hides what they type
  27. *
  28. * @param bool $allowFallback If prompting fails for any reason and this is set to true the prompt
  29. * will be done using the regular prompt() function, otherwise a
  30. * \RuntimeException is thrown.
  31. * @return string
  32. * @throws RuntimeException on failure to prompt, unless $allowFallback is true
  33. */
  34. public static function hiddenPrompt($allowFallback = false)
  35. {
  36. // handle windows
  37. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  38. // fallback to hiddeninput executable
  39. $exe = __DIR__.'\\..\\res\\hiddeninput.exe';
  40. // handle code running from a phar
  41. if ('phar:' === substr(__FILE__, 0, 5)) {
  42. $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
  43. // use stream_copy_to_stream instead of copy
  44. // to work around https://bugs.php.net/bug.php?id=64634
  45. $source = fopen($exe, 'r');
  46. $target = fopen($tmpExe, 'w+');
  47. stream_copy_to_stream($source, $target);
  48. fclose($source);
  49. fclose($target);
  50. unset($source, $target);
  51. $exe = $tmpExe;
  52. }
  53. $output = shell_exec($exe);
  54. // clean up
  55. if (isset($tmpExe)) {
  56. unlink($tmpExe);
  57. }
  58. if ($output !== null) {
  59. // output a newline to be on par with the regular prompt()
  60. echo PHP_EOL;
  61. return self::trimAnswer($output);
  62. }
  63. }
  64. if (file_exists('/usr/bin/env')) {
  65. // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
  66. $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
  67. foreach (array('bash', 'zsh', 'ksh', 'csh', 'sh') as $sh) {
  68. if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
  69. $shell = $sh;
  70. break;
  71. }
  72. }
  73. if (isset($shell)) {
  74. $readCmd = ($shell === 'csh') ? 'set mypassword = $<' : 'read -r mypassword';
  75. $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
  76. $output = shell_exec($command);
  77. if ($output !== null) {
  78. // output a newline to be on par with the regular prompt()
  79. echo PHP_EOL;
  80. return self::trimAnswer($output);
  81. }
  82. }
  83. }
  84. // not able to hide the answer
  85. if (!$allowFallback) {
  86. throw new \RuntimeException('Could not prompt for input in a secure fashion, aborting');
  87. }
  88. return self::prompt();
  89. }
  90. private static function trimAnswer($str)
  91. {
  92. return preg_replace('{\r?\n$}D', '', $str);
  93. }
  94. }