Skip to content

Commit 2728501

Browse files
deemonicclaude
andcommitted
fix: address CodeRabbit review findings on PR #48
- Preserve previous state in Blaspable::withoutBlaspChecking() for nested calls - Reject recursive pipeline driver configuration - Guard validation rule against non-string input - Respect except fields when middleware fields config is set - Apply severity filter before overlap dedup in PatternDriver - Apply severity filter before masking in RegexDriver - Use mb_strtolower/mb_strlen in PhoneticMatcher for UTF-8 safety - Remove unused Dictionary import from BlaspServiceProvider Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4e25fdf commit 2728501

7 files changed

Lines changed: 26 additions & 19 deletions

File tree

src/BlaspManager.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ public function createPipelineDriver(): DriverInterface
7878
$config = $this->app['config']->get('blasp.drivers.pipeline', []);
7979
$driverNames = $config['drivers'] ?? ['regex', 'phonetic'];
8080

81+
if (in_array('pipeline', $driverNames, true)) {
82+
throw new InvalidArgumentException('Pipeline driver cannot contain itself. Remove "pipeline" from blasp.drivers.pipeline.drivers.');
83+
}
84+
8185
$resolvedDrivers = array_map(
8286
fn (string $name) => $this->resolveDriver($name),
8387
$driverNames,

src/BlaspServiceProvider.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
use Illuminate\Support\ServiceProvider;
77
use Illuminate\Support\Str;
88
use Illuminate\Support\Stringable;
9-
use Blaspsoft\Blasp\Core\Dictionary;
10-
119
class BlaspServiceProvider extends ServiceProvider
1210
{
1311
public function boot(): void
@@ -53,6 +51,10 @@ public function register(): void
5351
protected function registerValidationRule(): void
5452
{
5553
$this->app['validator']->extend('blasp_check', function ($attribute, $value, $parameters) {
54+
if (!is_string($value) || $value === '') {
55+
return true;
56+
}
57+
5658
$language = $parameters[0] ?? config('blasp.language', config('blasp.default_language', 'english'));
5759

5860
$manager = $this->app->make('blasp');

src/Blaspable.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,13 @@ public function blaspResult(string $attribute): ?Result
9292

9393
public static function withoutBlaspChecking(Closure $callback): mixed
9494
{
95+
$previousState = static::$blaspCheckingDisabled;
9596
static::$blaspCheckingDisabled = true;
9697

9798
try {
9899
return $callback();
99100
} finally {
100-
static::$blaspCheckingDisabled = false;
101+
static::$blaspCheckingDisabled = $previousState;
101102
}
102103
}
103104
}

src/Core/Matchers/PhoneticMatcher.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ public function __construct(
1414
private float $maxDistanceRatio = 0.6,
1515
private array $phoneticFalsePositives = [],
1616
) {
17-
$this->phoneticFalsePositives = array_map('strtolower', $this->phoneticFalsePositives);
17+
$this->phoneticFalsePositives = array_map(fn($fp) => mb_strtolower($fp, 'UTF-8'), $this->phoneticFalsePositives);
1818
$this->buildIndex($profanities);
1919
}
2020

2121
private function buildIndex(array $profanities): void
2222
{
2323
foreach ($profanities as $word) {
24-
$lower = strtolower($word);
24+
$lower = mb_strtolower($word, 'UTF-8');
2525
if (mb_strlen($lower, 'UTF-8') < $this->minWordLength) {
2626
continue;
2727
}
@@ -62,7 +62,7 @@ public function match(string $word): ?string
6262

6363
foreach ($this->index[$code] as $profanity) {
6464
$distance = levenshtein($lower, $profanity);
65-
$maxLen = max(strlen($lower), strlen($profanity));
65+
$maxLen = max(mb_strlen($lower, 'UTF-8'), mb_strlen($profanity, 'UTF-8'));
6666
$threshold = (int) ceil($this->maxDistanceRatio * $maxLen);
6767

6868
if ($distance <= $threshold && $distance < $bestDistance) {

src/Drivers/PatternDriver.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ public function detect(string $text, Dictionary $dictionary, MaskStrategyInterfa
5757
}
5858
}
5959

60+
// Apply severity filter before dedup so shorter high-severity matches aren't swallowed
61+
$minimumSeverity = $options['severity'] ?? null;
62+
if ($minimumSeverity instanceof Severity) {
63+
$matchedWords = array_values(array_filter(
64+
$matchedWords,
65+
fn(MatchedWord $w) => $w->severity->isAtLeast($minimumSeverity)
66+
));
67+
}
68+
6069
// Deduplicate overlapping matches (longest-first already recorded)
6170
usort($matchedWords, fn($a, $b) => $a->position - $b->position ?: $b->length - $a->length);
6271
$deduplicated = [];
@@ -69,15 +78,6 @@ public function detect(string $text, Dictionary $dictionary, MaskStrategyInterfa
6978
}
7079
$matchedWords = $deduplicated;
7180

72-
// Apply severity filter
73-
$minimumSeverity = $options['severity'] ?? null;
74-
if ($minimumSeverity instanceof Severity) {
75-
$matchedWords = array_values(array_filter(
76-
$matchedWords,
77-
fn(MatchedWord $w) => $w->severity->isAtLeast($minimumSeverity)
78-
));
79-
}
80-
8181
// Rebuild cleanText from surviving matches (right-to-left)
8282
$cleanText = $text;
8383
$sorted = $matchedWords;

src/Drivers/RegexDriver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function detect(string $text, Dictionary $dictionary, MaskStrategyInterfa
109109
}
110110
}
111111

112-
// Apply severity filter if set
112+
// Apply severity filter before masking so low-severity matches don't suppress overlapping ones
113113
$minimumSeverity = $options['severity'] ?? null;
114114
if ($minimumSeverity instanceof Severity) {
115115
$matchedWords = array_values(array_filter(

src/Middleware/CheckProfanity.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ public function handle(Request $request, Closure $next, ?string $action = null,
2222
$fields = config('blasp.middleware.fields', ['*']);
2323
$except = config('blasp.middleware.except', ['password', 'email', '_token']);
2424

25-
$input = $request->except($except);
26-
2725
if ($fields !== ['*']) {
28-
$input = $request->only($fields);
26+
$input = collect($request->only($fields))->except($except)->all();
27+
} else {
28+
$input = $request->except($except);
2929
}
3030

3131
$textFields = $this->extractTextFields($input);

0 commit comments

Comments
 (0)