3 namespace BookStack\Util;
11 * Filter to ensure HTML input for description content remains simple and
12 * to a limited allow-list of elements and attributes.
13 * More for consistency and to prevent nuisance rather than for security
14 * (which would be done via a separate content filter and CSP).
16 class HtmlDescriptionFilter
19 * @var array<string, string[]>
21 protected static array $allowedAttrsByElements = [
23 'a' => ['href', 'title'],
32 public static function filterFromString(string $html): string
34 $doc = new HtmlDocument($html);
36 $topLevel = [...$doc->getBodyChildren()];
37 foreach ($topLevel as $child) {
38 /** @var DOMNode $child */
39 if ($child instanceof DOMElement) {
40 static::filterElement($child);
42 $child->parentNode->removeChild($child);
46 return $doc->getBodyInnerHtml();
49 protected static function filterElement(DOMElement $element): void
51 $elType = strtolower($element->tagName);
52 $allowedAttrs = static::$allowedAttrsByElements[$elType] ?? null;
53 if (is_null($allowedAttrs)) {
58 /** @var DOMNamedNodeMap $attrs */
59 $attrs = $element->attributes;
60 for ($i = $attrs->length - 1; $i >= 0; $i--) {
61 /** @var DOMAttr $attr */
62 $attr = $attrs->item($i);
63 $name = strtolower($attr->name);
64 if (!in_array($name, $allowedAttrs)) {
65 $element->removeAttribute($attr->name);
69 foreach ($element->childNodes as $child) {
70 if ($child instanceof DOMElement) {
71 static::filterElement($child);