Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 602cae9

Browse filesBrowse files
committed
Allow OutputFormatter::escape() to be used for escaping URLs used in <href>
- escape() now escapes `\` and `>` 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()`
1 parent 93ed7c3 commit 602cae9
Copy full SHA for 602cae9

8 files changed

+27
-26
lines changed

‎src/Symfony/Component/Console/Formatter/OutputFormatter.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Formatter/OutputFormatter.php
+10-13Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,13 @@ public function __clone()
3434
}
3535

3636
/**
37-
* Escapes "<" special char in given text.
37+
* Escapes "<" and ">" special chars in given text.
3838
*
39-
* @param string $text Text to escape
40-
*
41-
* @return string Escaped text
39+
* @return string
4240
*/
4341
public static function escape($text)
4442
{
45-
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
43+
$text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);
4644

4745
return self::escapeTrailingBackslash($text);
4846
}
@@ -144,9 +142,11 @@ public function formatAndWrap(string $message, int $width)
144142
{
145143
$offset = 0;
146144
$output = '';
147-
$tagRegex = '[a-z][^<>]*+';
145+
$openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
146+
$closeTagRegex = '[a-z][^<>]*+';
148147
$currentLineLength = 0;
149-
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
148+
preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
149+
150150
foreach ($matches[0] as $i => $match) {
151151
$pos = $match[1];
152152
$text = $match[0];
@@ -180,11 +180,7 @@ public function formatAndWrap(string $message, int $width)
180180

181181
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
182182

183-
if (str_contains($output, "\0")) {
184-
return strtr($output, ["\0" => '\\', '\\<' => '<']);
185-
}
186-
187-
return str_replace('\\<', '<', $output);
183+
return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
188184
}
189185

190186
/**
@@ -218,7 +214,8 @@ private function createStyleFromString(string $string): ?OutputFormatterStyleInt
218214
} elseif ('bg' == $match[0]) {
219215
$style->setBackground(strtolower($match[1]));
220216
} elseif ('href' === $match[0]) {
221-
$style->setHref($match[1]);
217+
$url = preg_replace('{\\\\([<>])}', '$1', $match[1]);
218+
$style->setHref($url);
222219
} elseif ('options' === $match[0]) {
223220
preg_match_all('([^,;]+)', strtolower($match[1]), $options);
224221
$options = array_shift($options);

‎src/Symfony/Component/Console/Tests/Fixtures/command_2.txt

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Fixtures/command_2.txt
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
command 2 description
33

44
<comment>Usage:</comment>
5-
descriptor:command2 [options] [--] \<argument_name>
6-
descriptor:command2 -o|--option_name \<argument_name>
7-
descriptor:command2 \<argument_name>
5+
descriptor:command2 [options] [--] \<argument_name\>
6+
descriptor:command2 -o|--option_name \<argument_name\>
7+
descriptor:command2 \<argument_name\>
88

99
<comment>Arguments:</comment>
1010
<info>argument_name</info>

‎src/Symfony/Component/Console/Tests/Fixtures/command_mbstring.txt

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Fixtures/command_mbstring.txt
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
command åèä description
33

44
<comment>Usage:</comment>
5-
descriptor:åèä [options] [--] \<argument_åèä>
6-
descriptor:åèä -o|--option_name \<argument_name>
7-
descriptor:åèä \<argument_name>
5+
descriptor:åèä [options] [--] \<argument_åèä\>
6+
descriptor:åèä -o|--option_name \<argument_name\>
7+
descriptor:åèä \<argument_name\>
88

99
<comment>Arguments:</comment>
1010
<info>argument_åèä</info>
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<info>argument_name</info> argument description<comment> [default: "\<comment>style\</>"]</comment>
1+
<info>argument_name</info> argument description<comment> [default: "\<comment\>style\</\>"]</comment>
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<info>-o, --option_name=OPTION_NAME</info> option description<comment> [default: "\<comment>style\</>"]</comment>
1+
<info>-o, --option_name=OPTION_NAME</info> option description<comment> [default: "\<comment\>style\</\>"]</comment>
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<info>-o, --option_name=OPTION_NAME</info> option description<comment> [default: ["\<comment>Hello\</comment>","\<info>world\</info>"]]</comment><comment> (multiple values allowed)</comment>
1+
<info>-o, --option_name=OPTION_NAME</info> option description<comment> [default: ["\<comment\>Hello\</comment\>","\<info\>world\</info\>"]]</comment><comment> (multiple values allowed)</comment>

‎src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ public function testLGCharEscaping()
3232
$this->assertEquals('foo << bar \\', $formatter->format('foo << bar \\'));
3333
$this->assertEquals("foo << \033[32mbar \\ baz\033[39m \\", $formatter->format('foo << <info>bar \\ baz</info> \\'));
3434
$this->assertEquals('<info>some info</info>', $formatter->format('\\<info>some info\\</info>'));
35-
$this->assertEquals('\\<info>some info\\</info>', OutputFormatter::escape('<info>some info</info>'));
35+
$this->assertEquals('\\<info\\>some info\\</info\\>', OutputFormatter::escape('<info>some info</info>'));
36+
// every < and > gets escaped if not already escaped, but already escaped ones do not get escaped again
37+
// and escaped backslashes remain as such, same with backslashes escaping non-special characters
38+
$this->assertEquals('foo \\< bar \\< baz \\\\< foo \\> bar \\> baz \\\\> \\x', OutputFormatter::escape('foo < bar \\< baz \\\\< foo > bar \\> baz \\\\> \\x'));
3639

3740
$this->assertEquals(
3841
"\033[33mSymfony\\Component\\Console does work very well!\033[39m",
@@ -259,6 +262,7 @@ public function provideDecoratedAndNonDecoratedOutput()
259262
['<question>some question</question>', 'some question', "\033[30;46msome question\033[39;49m"],
260263
['<fg=red>some text with inline style</>', 'some text with inline style', "\033[31msome text with inline style\033[39m"],
261264
['<href=idea://open/?file=/path/SomeFile.php&line=12>some URL</>', 'some URL', "\033]8;;idea://open/?file=/path/SomeFile.php&line=12\033\\some URL\033]8;;\033\\"],
265+
['<href=https://example.com/\<woohoo\>>some URL with \<woohoo\></>', 'some URL with <woohoo>', "\033]8;;https://example.com/<woohoo>\033\\some URL with <woohoo>\033]8;;\033\\"],
262266
['<href=idea://open/?file=/path/SomeFile.php&line=12>some URL</>', 'some URL', 'some URL', 'JetBrains-JediTerm'],
263267
];
264268
}

‎src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ public function testFormatBlockLGEscaping()
8383
$formatter = new FormatterHelper();
8484

8585
$this->assertEquals(
86-
'<error> </error>'."\n".
87-
'<error> \<info>some info\</info> </error>'."\n".
88-
'<error> </error>',
86+
'<error> </error>'."\n".
87+
'<error> \<info\>some info\</info\> </error>'."\n".
88+
'<error> </error>',
8989
$formatter->formatBlock('<info>some info</info>', 'error', true),
9090
'::formatBlock() escapes \'<\' chars'
9191
);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.