1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Services; 21 22use HTMLPurifier; 23use HTMLPurifier_AttrDef_Enum; 24use HTMLPurifier_Config; 25use HTMLPurifier_HTMLDefinition; 26 27use function assert; 28 29/** 30 * Filter/sanitize HTML 31 */ 32class HtmlService 33{ 34 /** 35 * Take some dirty HTML (as provided by the user), and clean it before 36 * we save/display it. 37 * 38 * @param string $html 39 * 40 * @return string 41 */ 42 public function sanitize(string $html): string 43 { 44 $config = HTMLPurifier_Config::createDefault(); 45 46 $config->set('Cache.DefinitionImpl', null); 47 48 $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now 49 50 // Remove the default maximum width/height for images. This enables percentage values. 51 $config->set('CSS.MaxImgLength', null); 52 53 // Allow id attributes. 54 $config->set('Attr.EnableID', true); 55 56 $def = $config->getHTMLDefinition(true); 57 assert($def instanceof HTMLPurifier_HTMLDefinition); 58 59 // Allow link targets. 60 $def->addAttribute('a', 'target', new HTMLPurifier_AttrDef_Enum(['_blank', '_self', '_target', '_top'])); 61 62 // Allow image maps. 63 $def->addAttribute('img', 'usemap', 'CDATA'); 64 65 $map = $def->addElement('map', 'Block', 'Flow', 'Common', [ 66 'name' => 'CDATA', 67 'id' => 'ID', 68 'title' => 'CDATA', 69 ]); 70 71 $map->excludes = ['map' => true]; 72 73 $area = $def->addElement('area', 'Block', 'Empty', 'Common', [ 74 'name' => 'CDATA', 75 'id' => 'ID', 76 'alt' => 'Text', 77 'coords' => 'CDATA', 78 'accesskey' => 'Character', 79 'nohref' => new HTMLPurifier_AttrDef_Enum(['nohref']), 80 'href' => 'URI', 81 'shape' => new HTMLPurifier_AttrDef_Enum(['rect', 'circle', 'poly', 'default']), 82 'tabindex' => 'Number', 83 ]); 84 85 $area->excludes = ['area' => true]; 86 87 // Allow audio and video 88 $audio = $def->addElement('audio', 'Block', 'Flow', 'Common', [ 89 'controls' => 'Bool#controls', 90 'src' => 'URI', 91 ]); 92 $audio->excludes = ['audio' => true]; 93 94 $video = $def->addElement('video', 'Block', 'Flow', 'Common', [ 95 'controls' => 'Bool#controls', 96 'height' => 'Number', 97 'poster' => 'URI', 98 'src' => 'URI', 99 'width' => 'Number', 100 ]); 101 $video->excludes = ['video' => true]; 102 103 $purifier = new HTMLPurifier($config); 104 105 return $purifier->purify($html); 106 } 107} 108