Make binary string output readable for mostly-printable values

This commit is contained in:
Sebastian Bergmann 2026-05-20 08:16:39 +02:00
parent 41cdd17bf0
commit c3c99d6629
No known key found for this signature in database
2 changed files with 58 additions and 10 deletions

View File

@ -28,11 +28,16 @@ use function is_resource;
use function is_string;
use function mb_strlen;
use function mb_substr;
use function ord;
use function preg_match;
use function preg_match_all;
use function preg_replace_callback;
use function spl_object_id;
use function sprintf;
use function str_contains;
use function str_repeat;
use function str_replace;
use function strlen;
use function strpbrk;
use function strtr;
use function var_export;
@ -437,21 +442,44 @@ final readonly class Exporter
private function exportString(string $value): string
{
// Match for most non-printable chars somewhat taking multibyte chars into account
if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value) === 1) {
$unprintableCount = preg_match_all('/[^\x09-\x0d\x1b\x20-\xff]/', $value);
if ($unprintableCount === false || $unprintableCount === 0) {
return "'" .
strtr(
$value,
[
"\r\n" => '\r\n' . "\n",
"\n\r" => '\n\r' . "\n",
"\r" => '\r' . "\n",
"\n" => '\n' . "\n",
],
) .
"'";
}
// A NUL byte or a high ratio of unprintable bytes signals truly
// binary data; keep the compact hex dump in those cases.
if (str_contains($value, "\x00") || ($unprintableCount / strlen($value)) > 0.3) {
return 'Binary String: 0x' . bin2hex($value);
}
return "'" .
strtr(
// Mostly printable: keep printable bytes visible and escape only
// the offending ones inline using PHP-style \xNN escapes.
return 'Binary String: "' .
preg_replace_callback(
'/[\x00-\x1f\x7f"\\\\]/',
static fn (array $m): string => match ($m[0]) {
"\t" => '\t',
"\n" => '\n',
"\r" => '\r',
'"' => '\"',
'\\' => '\\\\',
default => sprintf('\x%02x', ord($m[0])),
},
$value,
[
"\r\n" => '\r\n' . "\n",
"\n\r" => '\n\r' . "\n",
"\r" => '\r' . "\n",
"\n" => '\n' . "\n",
],
) .
"'";
'"';
}
/**

View File

@ -239,6 +239,26 @@ EOF,
'Binary String: 0x0009',
0,
],
'mostly printable string with a single control byte' => [
'Hello' . chr(0x01) . 'World',
'Binary String: "Hello\x01World"',
0,
],
'mostly printable string with several control bytes' => [
'BEGIN' . chr(0x01) . 'DATA' . chr(0x1F) . 'END',
'Binary String: "BEGIN\x01DATA\x1fEND"',
0,
],
'mixed string with embedded double quote and backslash' => [
'a"b\\c' . chr(0x07) . 'd',
'Binary String: "a\"b\\\\c\x07d"',
0,
],
'mixed string preserves UTF-8 high bytes' => [
'café' . chr(0x01),
'Binary String: "café\x01"',
0,
],
[
'',
"''",