ldc-boutique-cnc-sable/app/Http/Middleware/HtmlSanitizer.php
2026-10-27 20:20:52 +01:00

106 lines
3.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Mews\Purifier\Purifier;
use Symfony\Component\HttpFoundation\Response;
class HtmlSanitizer
{
public function __construct(
private readonly Purifier $purifierService
) {
}
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next
* @param string ...$params List of input names that should be sanitized with rich sanitization (allowing some HTML)
*/
public function handle(Request $request, \Closure $next, string ...$params): Response
{
$inputs = $request->all();
\array_walk($inputs, function (null | string | array | UploadedFile &$input, string $name) use ($params): void {
$this->cleanInput($input, $name, null, $params);
});
$request->merge($inputs);
return $next($request);
}
/**
* @param null|string|array<string,mixed>|\Illuminate\Http\UploadedFile $input
* @param string[] $params
*/
private function cleanInput(
null | string | array | UploadedFile &$input,
string $name,
?string $rootName,
array $params
): void {
if (is_array($input)) {
array_walk_recursive(
$input,
function (null | string | array | UploadedFile &$nestedInput, string $nestedName) use ($name, $params): void {
$this->cleanInput($nestedInput, $nestedName, $name, $params);
}
);
}
if (! is_string($input)) {
return;
}
if ($this->shouldApplyRichSanitization($name, $rootName, $params)) {
$this->sanitizeRichInput($input);
return;
}
$input = strip_tags($input);
}
/**
* @param array<int,string|string[]> $params
*/
private function shouldApplyRichSanitization(string $name, ?string $rootName, array $params): bool
{
return in_array($name, $params) || in_array($rootName ?? $name, $params);
}
private function sanitizeRichInput(string &$input): void
{
$isJson = \json_decode($input, true);
if ($isJson && is_array($isJson)) {
\array_walk_recursive($isJson, function (string | array &$item): void {
if (\is_string($item)) {
$item = $this->purifyString($item);
}
});
$input = json_encode($isJson, \JSON_THROW_ON_ERROR) ?: throw new \UnexpectedValueException('Failed to encode JSON after sanitization.');
return;
}
$input = $this->purifyString($input);
}
private function purifyString(string $input): string
{
$cleaned = $this->purifierService->clean($input);
if (! is_string($cleaned)) {
throw new \UnexpectedValueException('Purifier should return a string when input is a string.');
}
return ToolboxHelper::stripCkEditorSchemesOnMediaInputs($cleaned);
}
}