Skip to content

Commit e9af92f

Browse files
committed
[Console][Table] Fix invalid UTF-8 due to text wrapping
Fixes #58286
1 parent 0270060 commit e9af92f

File tree

3 files changed

+21
-6
lines changed

3 files changed

+21
-6
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Console\Formatter;
1313

1414
use Symfony\Component\Console\Exception\InvalidArgumentException;
15+
use Symfony\Component\Console\Helper\Helper;
1516

1617
use function Symfony\Component\String\b;
1718

@@ -146,9 +147,11 @@ public function formatAndWrap(?string $message, int $width)
146147
continue;
147148
}
148149

150+
// convert byte position to character position.
151+
$pos = Helper::length(substr($message, 0, $pos));
149152
// add the text up to the next tag
150-
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
151-
$offset = $pos + \strlen($text);
153+
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
154+
$offset = $pos + Helper::length($text);
152155

153156
// opening tag?
154157
if ($open = '/' !== $text[1]) {
@@ -169,7 +172,7 @@ public function formatAndWrap(?string $message, int $width)
169172
}
170173
}
171174

172-
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
175+
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset), $output, $width, $currentLineLength);
173176

174177
return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
175178
}
@@ -236,8 +239,8 @@ private function applyCurrentStyle(string $text, string $current, int $width, in
236239
}
237240

238241
if ($currentLineLength) {
239-
$prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
240-
$text = substr($text, $i);
242+
$prefix = Helper::substr($text, 0, $i = $width - $currentLineLength)."\n";
243+
$text = Helper::substr($text, $i);
241244
} else {
242245
$prefix = '';
243246
}
@@ -253,7 +256,7 @@ private function applyCurrentStyle(string $text, string $current, int $width, in
253256
$lines = explode("\n", $text);
254257

255258
foreach ($lines as $line) {
256-
$currentLineLength += \strlen($line);
259+
$currentLineLength += Helper::length($line);
257260
if ($width <= $currentLineLength) {
258261
$currentLineLength = 0;
259262
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ public static function substr(?string $string, int $from, ?int $length = null):
8686
{
8787
$string ??= '';
8888

89+
if (preg_match('//u', $string)) {
90+
$result = grapheme_substr((new UnicodeString($string))->toString(), $from, $length);
91+
92+
return false === $result
93+
? ''
94+
: $result;
95+
}
96+
8997
if (false === $encoding = mb_detect_encoding($string, null, true)) {
9098
return substr($string, $from, $length);
9199
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ public function testFormatAndWrap()
365365
$this->assertSame("Lore\nm \e[37;41mip\e[39;49m\n\e[37;41msum\e[39;49m \ndolo\nr \e[32msi\e[39m\n\e[32mt\e[39m am\net", $formatter->formatAndWrap('Lorem <error>ipsum</error> dolor <info>sit</info> amet', 4));
366366
$this->assertSame("Lorem \e[37;41mip\e[39;49m\n\e[37;41msum\e[39;49m dolo\nr \e[32msit\e[39m am\net", $formatter->formatAndWrap('Lorem <error>ipsum</error> dolor <info>sit</info> amet', 8));
367367
$this->assertSame("Lorem \e[37;41mipsum\e[39;49m dolor \e[32m\e[39m\n\e[32msit\e[39m, \e[37;41mamet\e[39;49m et \e[32mlauda\e[39m\n\e[32mntium\e[39m architecto", $formatter->formatAndWrap('Lorem <error>ipsum</error> dolor <info>sit</info>, <error>amet</error> et <info>laudantium</info> architecto', 18));
368+
$this->assertSame("\e[37;41mnon-empty-array\e[39;49m\e[37;41m<mixed, mixed>\e[39;49m given.\n🪪\n argument.type", $formatter->formatAndWrap("<error>non-empty-array<mixed, mixed></error> given.\n🪪 argument.type", 38));
369+
$this->assertSame("Usuário <strong>{{user_name}}</strong> não é válid\no.", $formatter->formatAndWrap('Usuário <strong>{{user_name}}</strong> não é válido.', 50));
368370

369371
$formatter = new OutputFormatter();
370372

@@ -376,6 +378,8 @@ public function testFormatAndWrap()
376378
$this->assertSame("Â rèälly\nlöng tîtlè\nthät cöüld\nnèêd\nmúltîplê\nlínès", $formatter->formatAndWrap('Â rèälly löng tîtlè thät cöüld nèêd múltîplê línès', 10));
377379
$this->assertSame("Â rèälly\nlöng tîtlè\nthät cöüld\nnèêd\nmúltîplê\n línès", $formatter->formatAndWrap("Â rèälly löng tîtlè thät cöüld nèêd múltîplê\n línès", 10));
378380
$this->assertSame('', $formatter->formatAndWrap(null, 5));
381+
$this->assertSame("non-empty-array<mixed, mixed> given.\n🪪\n argument.type", $formatter->formatAndWrap("<error>non-empty-array<mixed, mixed></error> given.\n🪪 argument.type", 38));
382+
$this->assertSame("Usuário <strong>{{user_name}}</strong> não é válid\no.", $formatter->formatAndWrap('Usuário <strong>{{user_name}}</strong> não é válido.', 50));
379383
}
380384
}
381385

0 commit comments

Comments
 (0)
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