- Introduced DOMPurify for sanitizing HTML inputs. - Added `sanitizeRichHtml` and `toSanitizedHtml` functions for safe HTML rendering. - Updated various components to use the new sanitization methods to prevent XSS attacks.
96 lines
2.8 KiB
PHP
96 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Middlewares;
|
|
|
|
use App\Lib\Helpers\ToolboxHelper;
|
|
use Illuminate\Http\Request;
|
|
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 (mixed &$input, int | string $name) use ($params): void {
|
|
$this->cleanInput($input, (string) $name, null, $params);
|
|
});
|
|
$request->merge($inputs);
|
|
|
|
return $next($request);
|
|
}
|
|
|
|
/**
|
|
* @param string[] $params
|
|
*/
|
|
private function cleanInput(mixed &$input, string $name, ?string $rootName, array $params): void
|
|
{
|
|
if (is_array($input)) {
|
|
array_walk_recursive($input, function (mixed &$nestedInput, int | string $nestedName) use ($name, $params): void {
|
|
$this->cleanInput($nestedInput, (string) $nestedName, $name, $params);
|
|
});
|
|
}
|
|
|
|
if (! is_string($input)) {
|
|
return;
|
|
}
|
|
|
|
if ($this->shouldApplyRichSanitization($name, $rootName, $params)) {
|
|
$this->sanitizeRichInput($input);
|
|
|
|
return;
|
|
}
|
|
|
|
$input = strip_tags($input);
|
|
}
|
|
|
|
/**
|
|
* @param 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);
|
|
}
|
|
}
|