From 451acdc5ca51f387cc23febb3d5f8f3775189f49 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 2 Jan 2022 10:41:36 +0100 Subject: [PATCH 1/7] Bump license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 9ff2d0d63..88bf75bb4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2021 Fabien Potencier +Copyright (c) 2004-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5edd78267c8fcce044de89fc73d81acdbccceac0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 5 Jan 2022 11:46:56 +0100 Subject: [PATCH 2/7] Allow OutputFormatter::escape() to be used for escaping URLs used in - escape() now escapes `>` as well as `<` - URLs containing escaped `<` and `>` are rendered correctly as is - user-provided URLs should now be safe to use (as in they cannot break the formatting) as long as they're piped through `escape()` --- Formatter/OutputFormatter.php | 18 ++++++++---------- Tests/Fixtures/command_2.txt | 6 +++--- Tests/Fixtures/command_mbstring.txt | 6 +++--- Tests/Fixtures/input_argument_with_style.txt | 2 +- Tests/Fixtures/input_option_with_style.txt | 2 +- .../Fixtures/input_option_with_style_array.txt | 2 +- Tests/Formatter/OutputFormatterTest.php | 6 +++++- Tests/Helper/FormatterHelperTest.php | 6 +++--- 8 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Formatter/OutputFormatter.php b/Formatter/OutputFormatter.php index 0f969c7ad..e8c10e700 100644 --- a/Formatter/OutputFormatter.php +++ b/Formatter/OutputFormatter.php @@ -34,7 +34,7 @@ public function __clone() } /** - * Escapes "<" special char in given text. + * Escapes "<" and ">" special chars in given text. * * @param string $text Text to escape * @@ -42,7 +42,7 @@ public function __clone() */ public static function escape($text) { - $text = preg_replace('/([^\\\\]?)])/', '$1\\\\$2', $text); return self::escapeTrailingBackslash($text); } @@ -144,9 +144,10 @@ public function formatAndWrap(string $message, int $width) { $offset = 0; $output = ''; - $tagRegex = '[a-z][^<>]*+'; + $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + $closeTagRegex = '[a-z][^<>]*+'; $currentLineLength = 0; - preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); + preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; @@ -180,11 +181,7 @@ public function formatAndWrap(string $message, int $width) $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); - if (str_contains($output, "\0")) { - return strtr($output, ["\0" => '\\', '\\<' => '<']); - } - - return str_replace('\\<', '<', $output); + return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); } /** @@ -218,7 +215,8 @@ private function createStyleFromString(string $string): ?OutputFormatterStyleInt } elseif ('bg' == $match[0]) { $style->setBackground(strtolower($match[1])); } elseif ('href' === $match[0]) { - $style->setHref($match[1]); + $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); + $style->setHref($url); } elseif ('options' === $match[0]) { preg_match_all('([^,;]+)', strtolower($match[1]), $options); $options = array_shift($options); diff --git a/Tests/Fixtures/command_2.txt b/Tests/Fixtures/command_2.txt index 45e7bec4d..fcab77a29 100644 --- a/Tests/Fixtures/command_2.txt +++ b/Tests/Fixtures/command_2.txt @@ -2,9 +2,9 @@ command 2 description Usage: - descriptor:command2 [options] [--] \ - descriptor:command2 -o|--option_name \ - descriptor:command2 \ + descriptor:command2 [options] [--] \ + descriptor:command2 -o|--option_name \ + descriptor:command2 \ Arguments: argument_name diff --git a/Tests/Fixtures/command_mbstring.txt b/Tests/Fixtures/command_mbstring.txt index 2fd51d057..1fa4e3135 100644 --- a/Tests/Fixtures/command_mbstring.txt +++ b/Tests/Fixtures/command_mbstring.txt @@ -2,9 +2,9 @@ command åèä description Usage: - descriptor:åèä [options] [--] \ - descriptor:åèä -o|--option_name \ - descriptor:åèä \ + descriptor:åèä [options] [--] \ + descriptor:åèä -o|--option_name \ + descriptor:åèä \ Arguments: argument_åèä diff --git a/Tests/Fixtures/input_argument_with_style.txt b/Tests/Fixtures/input_argument_with_style.txt index 35384a6be..79149ca69 100644 --- a/Tests/Fixtures/input_argument_with_style.txt +++ b/Tests/Fixtures/input_argument_with_style.txt @@ -1 +1 @@ - argument_name argument description [default: "\style\"] + argument_name argument description [default: "\style\"] diff --git a/Tests/Fixtures/input_option_with_style.txt b/Tests/Fixtures/input_option_with_style.txt index 880a53518..4bd30a662 100644 --- a/Tests/Fixtures/input_option_with_style.txt +++ b/Tests/Fixtures/input_option_with_style.txt @@ -1 +1 @@ - -o, --option_name=OPTION_NAME option description [default: "\style\"] + -o, --option_name=OPTION_NAME option description [default: "\style\"] diff --git a/Tests/Fixtures/input_option_with_style_array.txt b/Tests/Fixtures/input_option_with_style_array.txt index 265c18c5a..1fbb05b8a 100644 --- a/Tests/Fixtures/input_option_with_style_array.txt +++ b/Tests/Fixtures/input_option_with_style_array.txt @@ -1 +1 @@ - -o, --option_name=OPTION_NAME option description [default: ["\Hello\","\world\"]] (multiple values allowed) + -o, --option_name=OPTION_NAME option description [default: ["\Hello\","\world\"]] (multiple values allowed) diff --git a/Tests/Formatter/OutputFormatterTest.php b/Tests/Formatter/OutputFormatterTest.php index 1bd2b5d57..f418f446f 100644 --- a/Tests/Formatter/OutputFormatterTest.php +++ b/Tests/Formatter/OutputFormatterTest.php @@ -32,7 +32,10 @@ public function testLGCharEscaping() $this->assertEquals('foo << bar \\', $formatter->format('foo << bar \\')); $this->assertEquals("foo << \033[32mbar \\ baz\033[39m \\", $formatter->format('foo << bar \\ baz \\')); $this->assertEquals('some info', $formatter->format('\\some info\\')); - $this->assertEquals('\\some info\\', OutputFormatter::escape('some info')); + $this->assertEquals('\\some info\\', OutputFormatter::escape('some info')); + // every < and > gets escaped if not already escaped, but already escaped ones do not get escaped again + // and escaped backslashes remain as such, same with backslashes escaping non-special characters + $this->assertEquals('foo \\< bar \\< baz \\\\< foo \\> bar \\> baz \\\\> \\x', OutputFormatter::escape('foo < bar \\< baz \\\\< foo > bar \\> baz \\\\> \\x')); $this->assertEquals( "\033[33mSymfony\\Component\\Console does work very well!\033[39m", @@ -259,6 +262,7 @@ public function provideDecoratedAndNonDecoratedOutput() ['some question', 'some question', "\033[30;46msome question\033[39;49m"], ['some text with inline style', 'some text with inline style', "\033[31msome text with inline style\033[39m"], ['some URL', 'some URL', "\033]8;;idea://open/?file=/path/SomeFile.php&line=12\033\\some URL\033]8;;\033\\"], + ['>some URL with \', 'some URL with ', "\033]8;;https://example.com/\033\\some URL with \033]8;;\033\\"], ['some URL', 'some URL', 'some URL', 'JetBrains-JediTerm'], ]; } diff --git a/Tests/Helper/FormatterHelperTest.php b/Tests/Helper/FormatterHelperTest.php index 934e11ac1..c9a3c5e00 100644 --- a/Tests/Helper/FormatterHelperTest.php +++ b/Tests/Helper/FormatterHelperTest.php @@ -83,9 +83,9 @@ public function testFormatBlockLGEscaping() $formatter = new FormatterHelper(); $this->assertEquals( - ' '."\n". - ' \some info\ '."\n". - ' ', + ' '."\n". + ' \some info\ '."\n". + ' ', $formatter->formatBlock('some info', 'error', true), '::formatBlock() escapes \'<\' chars' ); From 358d679d5744bc731861976aa060f2a97b9406f7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 18 Jan 2022 10:12:43 +0100 Subject: [PATCH 3/7] [Console] use STDOUT/ERR in ConsoleOutput to save opening too many file descriptors --- Output/ConsoleOutput.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Output/ConsoleOutput.php b/Output/ConsoleOutput.php index 966fca099..484fcbdea 100644 --- a/Output/ConsoleOutput.php +++ b/Output/ConsoleOutput.php @@ -153,7 +153,8 @@ private function openOutputStream() return fopen('php://output', 'w'); } - return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + // Use STDOUT when possible to prevent from opening too many file descriptors + return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); } /** @@ -161,6 +162,11 @@ private function openOutputStream() */ private function openErrorStream() { - return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + if (!$this->hasStderrSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDERR when possible to prevent from opening too many file descriptors + return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); } } From aa58a696655593b0a012166cfbcf34fafac6e945 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 Jan 2022 11:57:37 +0100 Subject: [PATCH 4/7] [Console] fix parsing escaped chars in StringInput --- Input/StringInput.php | 23 +++++++++++++++++++---- Tests/Input/StringInputTest.php | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Input/StringInput.php b/Input/StringInput.php index eb5c07fdd..76f1d5030 100644 --- a/Input/StringInput.php +++ b/Input/StringInput.php @@ -24,7 +24,7 @@ */ class StringInput extends ArgvInput { - public const REGEX_STRING = '([^\s]+?)(?:\s|(?tokenize() parses long options with a value'], ["--long-option='foo bar'\"another\"", ['--long-option=foo baranother'], '->tokenize() parses long options with a value'], ['foo -a -ffoo --long bar', ['foo', '-a', '-ffoo', '--long', 'bar'], '->tokenize() parses when several arguments and options'], + ["--arg=\\\"'Jenny'\''s'\\\"", ["--arg=\"Jenny's\""], '->tokenize() parses quoted quotes'], ]; } From a89045900ae02d0f1b67ce9e627c3454fc25f0ff Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jan 2022 20:04:48 +0100 Subject: [PATCH 5/7] [Console] fix restoring stty mode on CTRL+C --- Application.php | 10 +++++++ Helper/QuestionHelper.php | 13 ++++++-- Tests/ApplicationTest.php | 35 ++++++++++++++++++++++ Tests/Fixtures/application_signalable.php | 36 +++++++++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 Tests/Fixtures/application_signalable.php diff --git a/Application.php b/Application.php index 9f9f8394e..c99a3f7ef 100644 --- a/Application.php +++ b/Application.php @@ -952,6 +952,16 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); } + if (Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + foreach ([\SIGINT, \SIGTERM] as $signal) { + $this->signalRegistry->register($signal, static function () use ($sttyMode) { + shell_exec('stty '.$sttyMode); + }); + } + } + if ($this->dispatcher) { foreach ($this->signalsToDispatchEvent as $signal) { $event = new ConsoleSignalEvent($command, $input, $output, $signal); diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index 28931358d..20ac09a1e 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -248,6 +248,9 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu $numMatches = \count($matches); $sttyMode = shell_exec('stty -g'); + $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null); + $r = [$inputStream]; + $w = []; // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); @@ -257,11 +260,15 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu // Read a keypress while (!feof($inputStream)) { + while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) { + // Give signal handlers a chance to run + $r = [$inputStream]; + } $c = fread($inputStream, 1); // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { - shell_exec(sprintf('stty %s', $sttyMode)); + shell_exec('stty '.$sttyMode); throw new MissingInputException('Aborted.'); } elseif ("\177" === $c) { // Backspace Character if (0 === $numMatches && 0 !== $i) { @@ -366,7 +373,7 @@ function ($match) use ($ret) { } // Reset stty so it behaves normally again - shell_exec(sprintf('stty %s', $sttyMode)); + shell_exec('stty '.$sttyMode); return $fullChoice; } @@ -427,7 +434,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $ $value = fgets($inputStream, 4096); if (self::$stty && Terminal::hasSttyAvailable()) { - shell_exec(sprintf('stty %s', $sttyMode)); + shell_exec('stty '.$sttyMode); } if (false === $value) { diff --git a/Tests/ApplicationTest.php b/Tests/ApplicationTest.php index 542130569..15319c3d4 100644 --- a/Tests/ApplicationTest.php +++ b/Tests/ApplicationTest.php @@ -38,9 +38,11 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\SignalRegistry\SignalRegistry; +use Symfony\Component\Console\Terminal; use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Process\Process; class ApplicationTest extends TestCase { @@ -1882,6 +1884,39 @@ public function testSignalableCommandInterfaceWithoutSignals() $application->add($command); $this->assertSame(0, $application->run(new ArrayInput(['signal']))); } + + /** + * @group tty + */ + public function testSignalableRestoresStty() + { + if (!Terminal::hasSttyAvailable()) { + $this->markTestSkipped('stty not available'); + } + + if (!SignalRegistry::isSupported()) { + $this->markTestSkipped('pcntl signals not available'); + } + + $previousSttyMode = shell_exec('stty -g'); + + $p = new Process(['php', __DIR__.'/Fixtures/application_signalable.php']); + $p->setTty(true); + $p->start(); + + for ($i = 0; $i < 10 && shell_exec('stty -g') === $previousSttyMode; ++$i) { + usleep(100000); + } + + $this->assertNotSame($previousSttyMode, shell_exec('stty -g')); + $p->signal(\SIGINT); + $p->wait(); + + $sttyMode = shell_exec('stty -g'); + shell_exec('stty '.$previousSttyMode); + + $this->assertSame($previousSttyMode, $sttyMode); + } } class CustomApplication extends Application diff --git a/Tests/Fixtures/application_signalable.php b/Tests/Fixtures/application_signalable.php new file mode 100644 index 000000000..0194703b2 --- /dev/null +++ b/Tests/Fixtures/application_signalable.php @@ -0,0 +1,36 @@ +setCode(function(InputInterface $input, OutputInterface $output) { + $this->getHelper('question') + ->ask($input, $output, new ChoiceQuestion('😊', ['y'])); + + return 0; + }) + ->run() + +; From 32ba2ac82767a50959f826e3d64c27923070a85b Mon Sep 17 00:00:00 2001 From: BrokenSourceCode Date: Tue, 25 Jan 2022 23:56:21 +0100 Subject: [PATCH 6/7] [Console] Fix PHP 8.1 deprecation in ChoiceQuestion --- Question/ChoiceQuestion.php | 6 +++--- Tests/Question/ChoiceQuestionTest.php | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Question/ChoiceQuestion.php b/Question/ChoiceQuestion.php index 72703fb16..6247ca716 100644 --- a/Question/ChoiceQuestion.php +++ b/Question/ChoiceQuestion.php @@ -131,18 +131,18 @@ private function getDefaultValidator(): callable return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { if ($multiselect) { // Check for a separated comma values - if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) { + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } - $selectedChoices = explode(',', $selected); + $selectedChoices = explode(',', (string) $selected); } else { $selectedChoices = [$selected]; } if ($this->isTrimmable()) { foreach ($selectedChoices as $k => $v) { - $selectedChoices[$k] = trim($v); + $selectedChoices[$k] = trim((string) $v); } } diff --git a/Tests/Question/ChoiceQuestionTest.php b/Tests/Question/ChoiceQuestionTest.php index 9db12f852..327f69ad7 100644 --- a/Tests/Question/ChoiceQuestionTest.php +++ b/Tests/Question/ChoiceQuestionTest.php @@ -19,14 +19,15 @@ class ChoiceQuestionTest extends TestCase /** * @dataProvider selectUseCases */ - public function testSelectUseCases($multiSelect, $answers, $expected, $message) + public function testSelectUseCases($multiSelect, $answers, $expected, $message, $default = null) { $question = new ChoiceQuestion('A question', [ 'First response', 'Second response', 'Third response', 'Fourth response', - ]); + null, + ], $default); $question->setMultiselect($multiSelect); @@ -59,6 +60,19 @@ public function selectUseCases() ['First response', 'Second response'], 'When passed multiple answers on MultiSelect, the defaultValidator must return these answers as an array', ], + [ + false, + [null], + null, + 'When used null as default single answer on singleSelect, the defaultValidator must return this answer as null', + ], + [ + false, + ['First response'], + 'First response', + 'When used a string as default single answer on singleSelect, the defaultValidator must return this answer as a string', + 'First response', + ], ]; } From 0259f01dbf9d77badddbbf4c2abb681f24c9cac6 Mon Sep 17 00:00:00 2001 From: James Gilliland Date: Thu, 23 Sep 2021 10:00:15 -0500 Subject: [PATCH 7/7] Silence isatty warnings during tty detection --- Helper/QuestionHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Helper/QuestionHelper.php b/Helper/QuestionHelper.php index 0516545bc..a4754b824 100644 --- a/Helper/QuestionHelper.php +++ b/Helper/QuestionHelper.php @@ -485,11 +485,11 @@ private function isInteractiveInput($inputStream): bool } if (\function_exists('stream_isatty')) { - return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r')); + return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r')); } if (\function_exists('posix_isatty')) { - return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r')); + return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r')); } if (!\function_exists('exec')) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy