Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions formwork/src/Sanitizer/DomSanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,22 +231,30 @@ protected function sanitizeNodeAttributes(DOMElement $domElement): void
continue;
}

if (!in_array($attribute->nodeName, $this->allowedAttributes, true)) {
$domElement->removeAttribute($attribute->nodeName);
}
$this->sanitizeNodeAttribute($domElement, $attribute);
}
}

if (in_array($attribute->nodeName, $this->uriAttributes, true)) {
$uri = $this->sanitizeUri((string) $attribute->nodeValue);
/**
* Sanitize a single DOM element attribute
*/
protected function sanitizeNodeAttribute(DOMElement $domElement, DOMAttr $domAttr): void
{
if (!in_array($domAttr->nodeName, $this->allowedAttributes, true)) {
$domElement->removeAttribute($domAttr->nodeName);
}

$scheme = Uri::scheme($uri);
if (in_array($domAttr->nodeName, $this->uriAttributes, true)) {
$uri = $this->sanitizeUri((string) $domAttr->nodeValue);
Comment on lines +243 to +248

if ($scheme === null && !Str::startsWith($uri, '//')) {
continue;
}
$scheme = Uri::scheme($uri);

if (!$this->allowExternalUris || !in_array($scheme, $this->allowedUriSchemes, true)) {
$domElement->removeAttribute($attribute->nodeName);
}
if ($scheme === null && !Str::startsWith($uri, '//')) {
return;
}

if (!$this->allowExternalUris || !in_array($scheme, $this->allowedUriSchemes, true)) {
$domElement->removeAttribute($domAttr->nodeName);
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions formwork/src/Sanitizer/Reference/SvgReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,17 @@ class SvgReference
'href',
'xlink:href',
];

/**
* SMIL elements that require handling of the `attributeName` attribute to ensure it is safe
*
* @var list<string>
*/
public const array SMIL_ELEMENTS = [
'animate',
'animateMotion',
'animateTransform',
'discard',
'set',
];
}
27 changes: 27 additions & 0 deletions formwork/src/Sanitizer/SvgSanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class SvgSanitizer extends DomSanitizer

protected array $uriAttributes = SvgReference::URI_ATTRIBUTES;

/**
* @var list<string>
*/
protected array $smilElements = SvgReference::SMIL_ELEMENTS;

public function __construct(
protected DomParserInterface $domParser = new PhpDomParser(),
) {}
Expand All @@ -42,6 +47,19 @@ protected function sanitizeDocumentFragment(DOMDocumentFragment $domDocumentFrag
$this->addExplicitSvgNamespace($domDocumentFragment);
}

protected function sanitizeNodeAttribute(DOMElement $domElement, DOMAttr $domAttr): void
{
parent::sanitizeNodeAttribute($domElement, $domAttr);

if (
in_array($domElement->nodeName, $this->smilElements, true)
&& $domAttr->name === 'attributeName'
&& !$this->isSafeSmilAttributeName($domAttr->value)
) {
$domElement->removeAttribute($domAttr->name);
}
}

protected function addExplicitSvgNamespace(DOMDocumentFragment $domDocumentFragment): void
{
$svg = $domDocumentFragment->firstElementChild;
Expand Down Expand Up @@ -80,4 +98,13 @@ protected function addExplicitSvgNamespace(DOMDocumentFragment $domDocumentFragm

$svg->replaceWith($domElement);
}

/**
* Return whether the SMIL `attributeName` is allowed and not a URI attribute (which would be unsafe)
*/
private function isSafeSmilAttributeName(string $attributeName): bool
{
return in_array($attributeName, $this->allowedAttributes, true)
&& !in_array($attributeName, $this->uriAttributes, true);
}
}
Loading