From 09d05ee4849b4b44ff489916228a1e518c287aa6 Mon Sep 17 00:00:00 2001 From: chadicus Date: Thu, 15 Oct 2026 17:42:14 -0400 Subject: [PATCH 1/6] Add String::stripControlCharacters --- src/Filter/Strings.php | 21 +++++++++++++++++++++ tests/Filter/StringsTest.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/Filter/Strings.php b/src/Filter/Strings.php index 3f3c345..40cfd90 100644 --- a/src/Filter/Strings.php +++ b/src/Filter/Strings.php @@ -213,6 +213,27 @@ public static function stripEmoji(string $value, string $replacement = ''): stri return preg_replace($regexPatterns, $replacement, $value); } + /** + * Strip control characters from a given string + * + * @param string $value The input string. + * @param string $replacement The string to replace the tags with. Defaults to an empty string. + * + * @return string; + */ + public static function stripControlCharacters(string $value, string $replacement = ''): string + { + $allAsciiControlCharacters = '/[\x00-\x1F\x7F]/'; + $invisibleUnicodeControlCharacters = '/[\p{C}]/u'; + + $regexPatterns = [ + $allAsciiControlCharacters, + $invisibleUnicodeControlCharacters, + ]; + + return preg_replace($regexPatterns, $replacement, $value); + } + private static function validateMinimumLength(int $minLength) { if ($minLength < 0) { diff --git a/tests/Filter/StringsTest.php b/tests/Filter/StringsTest.php index 871298c..b4fef12 100644 --- a/tests/Filter/StringsTest.php +++ b/tests/Filter/StringsTest.php @@ -591,4 +591,39 @@ public static function provideStripEmoji(): array ], ]; } + + /** + * @param string $input The string value to be filtered. + * @param string $expectedValue The expected filtered value. + * + * @test + * @covers ::stripControlCharacters + * @dataProvider provideStripControlCharacters + */ + public function stripControlCharacters(string $input, string $expectedValue): void + { + $actualValue = Strings::stripControlCharacters($input); + $this->assertSame($expectedValue, $actualValue); + } + + /** + * @return array + */ + public static function provideStripControlCharacters(): array + { + return [ + 'string contains no control characters' => [ + 'input' => 'a b c', + 'expected' => 'a b c', + ], + 'Contains a null byte, tab, newline, and delete' => [ + 'input' => "Hello\x00\tWorld\nTest\x7F", + 'expected' => 'HelloWorldTest', + ], + 'Contains a Zero-Width Space (\u{200B}) and a Soft Hyphen (\u{00AD})' => [ + 'input' => "Uni\u{200B}code\u{00AD}Text", + 'expected' => 'UnicodeText', + ], + ]; + } } From 579ea829c8b3bd4af2ebc765f1fe480ed5916f4d Mon Sep 17 00:00:00 2001 From: chadicus Date: Thu, 15 Oct 2026 17:42:29 -0400 Subject: [PATCH 2/6] Add missing covers tag --- tests/Filter/StringsTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Filter/StringsTest.php b/tests/Filter/StringsTest.php index b4fef12..97ae327 100644 --- a/tests/Filter/StringsTest.php +++ b/tests/Filter/StringsTest.php @@ -553,6 +553,7 @@ public function provideRedactFailsOnBadInput() : array * @param string $expectedValue The expected filtered value. * * @test + * @covers ::stripEmoji * @dataProvider provideStripEmoji */ public function stripEmoji(string $input, string $expectedValue): void From b135ed54cbf7839456f138505e089157504f2973 Mon Sep 17 00:00:00 2001 From: chadicus Date: Thu, 15 Oct 2026 17:47:56 -0400 Subject: [PATCH 3/6] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f1bb5d7..b10c335 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,18 @@ $value = \TraderInteractive\Filter\Strings::redact('a string with some unwanted assert($value === 'a string with some ******** *****'); ``` +#### Strings::stripControlCharacters + +This filter will strip all ASCII control characters, plus the delete character, including tabs and newlines. +It will also strip unicode invisible formatting characters, private use characters, and unused code points. + +The second, optional argument specifies a replacement string for the removed characters. + +```php +$value = \TraderInteractive\Filter\Strings::stripControlCharacters("Hello\x00\tWorld\nTest\x7F"); +assert($value === 'HelloWorldTest'); +``` + #### Strings::stripEmoji This filter will strip emoji, pictographs, alphanumeric supplement characters and more from a given string. From 18bfcb6e93e81224335a87c7703991b05e8530e1 Mon Sep 17 00:00:00 2001 From: chadicus Date: Fri, 16 Oct 2026 08:47:27 -0400 Subject: [PATCH 4/6] Use single combined pattern for preg_replace call --- src/Filter/Strings.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Filter/Strings.php b/src/Filter/Strings.php index 40cfd90..ae48c10 100644 --- a/src/Filter/Strings.php +++ b/src/Filter/Strings.php @@ -223,15 +223,8 @@ public static function stripEmoji(string $value, string $replacement = ''): stri */ public static function stripControlCharacters(string $value, string $replacement = ''): string { - $allAsciiControlCharacters = '/[\x00-\x1F\x7F]/'; - $invisibleUnicodeControlCharacters = '/[\p{C}]/u'; - - $regexPatterns = [ - $allAsciiControlCharacters, - $invisibleUnicodeControlCharacters, - ]; - - return preg_replace($regexPatterns, $replacement, $value); + $controlCharactersPattern = '/[\x00-\x1F\x7F\p{C}]/u'; + return preg_replace($controlCharactersPattern, $replacement, $value); } private static function validateMinimumLength(int $minLength) From 379673d47a78310b002d9d95f98a03b3384d635d Mon Sep 17 00:00:00 2001 From: chadicus Date: Fri, 16 Oct 2026 08:58:45 -0400 Subject: [PATCH 5/6] Handle preg_replace error --- src/Filter/Strings.php | 7 ++++++- tests/Filter/StringsTest.php | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Filter/Strings.php b/src/Filter/Strings.php index ae48c10..b7941b8 100644 --- a/src/Filter/Strings.php +++ b/src/Filter/Strings.php @@ -224,7 +224,12 @@ public static function stripEmoji(string $value, string $replacement = ''): stri public static function stripControlCharacters(string $value, string $replacement = ''): string { $controlCharactersPattern = '/[\x00-\x1F\x7F\p{C}]/u'; - return preg_replace($controlCharactersPattern, $replacement, $value); + $filtered = preg_replace($controlCharactersPattern, $replacement, $value); + if ($filtered === null) { + throw new FilterException("Unable to strip control characters from '{$value}'"); + } + + return $filtered; } private static function validateMinimumLength(int $minLength) diff --git a/tests/Filter/StringsTest.php b/tests/Filter/StringsTest.php index 97ae327..e088f31 100644 --- a/tests/Filter/StringsTest.php +++ b/tests/Filter/StringsTest.php @@ -627,4 +627,16 @@ public static function provideStripControlCharacters(): array ], ]; } + + /** + * @test + * @covers ::stripControlCharacters + */ + public function stripControlCharactersNonUtf8() + { + $windows1252String = "100 \x80"; + $this->expectException(FilterException::class); + $this->expectExceptionMessage("Unable to strip control characters from '{$windows1252String}'"); + Strings::stripControlCharacters($windows1252String); + } } From a07311d96465739ceb1d5b54c9e2e38268ecb04c Mon Sep 17 00:00:00 2001 From: chadicus Date: Fri, 16 Oct 2026 09:05:28 -0400 Subject: [PATCH 6/6] Use separate verbose variables to better explain code --- src/Filter/Strings.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Filter/Strings.php b/src/Filter/Strings.php index b7941b8..a6bc289 100644 --- a/src/Filter/Strings.php +++ b/src/Filter/Strings.php @@ -223,7 +223,9 @@ public static function stripEmoji(string $value, string $replacement = ''): stri */ public static function stripControlCharacters(string $value, string $replacement = ''): string { - $controlCharactersPattern = '/[\x00-\x1F\x7F\p{C}]/u'; + $asciiControlCharacters = '\x00-\x1F\x7F'; + $unicodeControlCharacters = '\p{C}'; + $controlCharactersPattern = "/[{$asciiControlCharacters}{$unicodeControlCharacters}]/u"; $filtered = preg_replace($controlCharactersPattern, $replacement, $value); if ($filtered === null) { throw new FilterException("Unable to strip control characters from '{$value}'");