106 lines
3.1 KiB
PHP
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);
|
|
}
|
|
}
|