Skip to content

Commit 5e1e0fc

Browse files
deemonicclaude
andauthored
fix: add unicode flag to regex to prevent accented character false positives (#43)
* fix: add unicode flag to regex to prevent accented character false positives (#24) Multi-byte UTF-8 characters (ê, é) were matched byte-by-byte without the /u flag, causing false profanity detections for words like "tête" and "aré". Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: validate UTF-8 input before regex matching Ensures invalid UTF-8 strings are sanitized before reaching preg_match with the /u flag, preventing silent regex failures. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0f4b293 commit 5e1e0fc

4 files changed

Lines changed: 43 additions & 3 deletions

File tree

‎src/BlaspService.php‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ public function check(?string $string): self
261261
return $this;
262262
}
263263

264+
if (!mb_check_encoding($string, 'UTF-8')) {
265+
$string = mb_convert_encoding($string, 'UTF-8', 'UTF-8');
266+
}
267+
264268
$this->sourceString = $string;
265269

266270
$this->cleanString = $string;

‎src/Generators/ProfanityExpressionGenerator.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public function generateProfanityExpression(string $profanity, array $substituti
153153
}
154154

155155
$expression = str_replace(self::SEPARATOR_PLACEHOLDER, $separatorExpression, $expression);
156-
$expression = '/' . $expression . '/i';
156+
$expression = '/' . $expression . '/iu';
157157

158158
return $expression;
159159
}

‎tests/Issue24Test.php‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Blaspsoft\Blasp\Tests;
4+
5+
use Blaspsoft\Blasp\BlaspService;
6+
7+
class Issue24Test extends TestCase
8+
{
9+
public function test_etre_not_flagged_as_profanity()
10+
{
11+
$service = new BlaspService();
12+
$result = $service->check('Le cadre pourrait être un peu mieux');
13+
$this->assertFalse($result->hasProfanity(), 'être should not be flagged. Found: ' . implode(', ', $result->getUniqueProfanitiesFound()));
14+
}
15+
16+
public function test_are_accent_not_flagged()
17+
{
18+
$service = new BlaspService();
19+
$result = $service->check('aré');
20+
$this->assertFalse($result->hasProfanity(), 'aré should not be flagged. Found: ' . implode(', ', $result->getUniqueProfanitiesFound()));
21+
}
22+
23+
public function test_tete_not_flagged()
24+
{
25+
$service = new BlaspService();
26+
$result = $service->check('tête tete');
27+
$this->assertFalse($result->hasProfanity(), 'tête should not be flagged. Found: ' . implode(', ', $result->getUniqueProfanitiesFound()));
28+
}
29+
30+
public function test_actual_profanity_still_detected()
31+
{
32+
$service = new BlaspService();
33+
$result = $service->check('shit');
34+
$this->assertTrue($result->hasProfanity(), 'Actual profanity should still be detected after unicode fix');
35+
}
36+
}

‎tests/ProfanityExpressionGeneratorTest.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function test_generate_profanity_expression_simple()
6565

6666
$this->assertIsString($result);
6767
$this->assertStringStartsWith('/', $result);
68-
$this->assertStringEndsWith('/i', $result);
68+
$this->assertStringEndsWith('/iu', $result);
6969
}
7070

7171
public function test_generate_expressions_full_flow()
@@ -92,7 +92,7 @@ public function test_generate_expressions_full_flow()
9292
foreach ($result as $profanity => $expression) {
9393
$this->assertIsString($expression);
9494
$this->assertStringStartsWith('/', $expression);
95-
$this->assertStringEndsWith('/i', $expression);
95+
$this->assertStringEndsWith('/iu', $expression);
9696

9797
// Verify it's a valid regex by testing it doesn't throw error
9898
$testResult = @preg_match($expression, $profanity);

0 commit comments

Comments
 (0)