102 lines
3.4 KiB
PHP
102 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace humhub\modules\space_profiles\helpers;
|
|
|
|
use yii\helpers\HtmlPurifier;
|
|
|
|
class ProfileHtmlSanitizer
|
|
{
|
|
public static function sanitize(?string $html): string
|
|
{
|
|
$value = (string)$html;
|
|
if ($value === '') {
|
|
return '';
|
|
}
|
|
|
|
$styleBlocks = [];
|
|
if (preg_match_all('#<style\\b[^>]*>(.*?)</style>#is', $value, $matches)) {
|
|
$styleBlocks = $matches[1] ?? [];
|
|
}
|
|
$value = preg_replace('#<style\\b[^>]*>.*?</style>#is', '', $value) ?? '';
|
|
|
|
$value = preg_replace('#<script\\b[^>]*>.*?</script>#is', '', $value) ?? '';
|
|
$value = preg_replace("/\\son[a-z]+\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)/i", '', $value) ?? '';
|
|
$value = preg_replace('/javascript\\s*:/i', '', $value) ?? '';
|
|
$value = preg_replace('/expression\\s*\\(/i', '', $value) ?? '';
|
|
|
|
$purified = HtmlPurifier::process($value, [
|
|
'HTML.Allowed' => 'div,p,br,span,strong,em,b,i,u,ul,ol,li,h1,h2,h3,h4,h5,h6,a[href|title|target],img[src|alt|title|width|height],blockquote,hr,table,thead,tbody,tr,th,td,code,pre',
|
|
'Attr.EnableID' => false,
|
|
'CSS.Trusted' => false,
|
|
'URI.AllowedSchemes' => [
|
|
'http' => true,
|
|
'https' => true,
|
|
'mailto' => true,
|
|
],
|
|
]);
|
|
|
|
$scopedCss = [];
|
|
foreach ($styleBlocks as $styleBlock) {
|
|
$css = self::scopeCss((string)$styleBlock);
|
|
if (trim($css) !== '') {
|
|
$scopedCss[] = $css;
|
|
}
|
|
}
|
|
|
|
if (empty($scopedCss)) {
|
|
return $purified;
|
|
}
|
|
|
|
return $purified . "\n<style>\n" . implode("\n", $scopedCss) . "\n</style>";
|
|
}
|
|
|
|
private static function scopeCss(string $css): string
|
|
{
|
|
$css = preg_replace('/@import\\s+[^;]+;/i', '', $css) ?? '';
|
|
$css = preg_replace('/javascript\\s*:/i', '', $css) ?? '';
|
|
$css = preg_replace('/expression\\s*\\(/i', '', $css) ?? '';
|
|
|
|
$chunks = explode('}', $css);
|
|
$scoped = [];
|
|
|
|
foreach ($chunks as $chunk) {
|
|
if (trim($chunk) === '' || strpos($chunk, '{') === false) {
|
|
continue;
|
|
}
|
|
|
|
[$selectors, $body] = explode('{', $chunk, 2);
|
|
$selectors = trim($selectors);
|
|
if ($selectors === '') {
|
|
continue;
|
|
}
|
|
|
|
if (strpos($selectors, '@') === 0) {
|
|
$scoped[] = $selectors . '{' . $body . '}';
|
|
continue;
|
|
}
|
|
|
|
$selectorParts = array_filter(array_map('trim', explode(',', $selectors)));
|
|
$scopedSelectors = [];
|
|
|
|
foreach ($selectorParts as $selector) {
|
|
$selector = str_replace(':root', '.rescue-profile-scope', $selector);
|
|
if (preg_match('/^(html|body)\\b/i', $selector)) {
|
|
$selector = preg_replace('/^(html|body)\\b/i', '.rescue-profile-scope', $selector) ?? '.rescue-profile-scope';
|
|
}
|
|
|
|
if (strpos($selector, '.rescue-profile-scope') !== 0) {
|
|
$selector = '.rescue-profile-scope ' . $selector;
|
|
}
|
|
|
|
$scopedSelectors[] = $selector;
|
|
}
|
|
|
|
if (!empty($scopedSelectors)) {
|
|
$scoped[] = implode(', ', $scopedSelectors) . '{' . $body . '}';
|
|
}
|
|
}
|
|
|
|
return implode("\\n", $scoped);
|
|
}
|
|
}
|