mirror of
https://github.com/Piwigo/Piwigo.git
synced 2026-06-01 20:04:51 +02:00
Fixes #2552 - Implement SVG validation
Use https://github.com/darylldoyle/svg-sanitizer to check for malicious code inside svg, throw an error code 415 when triggered in the API.
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
namespace enshrined\svgSanitize\ElementReference;
|
||||
|
||||
use enshrined\svgSanitize\data\XPath;
|
||||
use enshrined\svgSanitize\Exceptions\NestingException;
|
||||
use enshrined\svgSanitize\Helper;
|
||||
|
||||
class Resolver
|
||||
{
|
||||
/**
|
||||
* @var XPath
|
||||
*/
|
||||
protected $xPath;
|
||||
|
||||
/**
|
||||
* @var Subject[]
|
||||
*/
|
||||
protected $subjects = [];
|
||||
|
||||
/**
|
||||
* @var array DOMElement[]
|
||||
*/
|
||||
protected $elementsToRemove = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $useNestingLimit;
|
||||
|
||||
public function __construct(XPath $xPath, $useNestingLimit)
|
||||
{
|
||||
$this->xPath = $xPath;
|
||||
$this->useNestingLimit = $useNestingLimit;
|
||||
}
|
||||
|
||||
public function collect()
|
||||
{
|
||||
$this->collectIdentifiedElements();
|
||||
$this->processReferences();
|
||||
$this->determineInvalidSubjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves one subject by element.
|
||||
*
|
||||
* @param \DOMElement $element
|
||||
* @param bool $considerChildren Whether to search in Subject's children as well
|
||||
* @return Subject|null
|
||||
*/
|
||||
public function findByElement(\DOMElement $element, $considerChildren = false)
|
||||
{
|
||||
foreach ($this->subjects as $subject) {
|
||||
if (
|
||||
$element === $subject->getElement()
|
||||
|| $considerChildren && Helper::isElementContainedIn($element, $subject->getElement())
|
||||
) {
|
||||
return $subject;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves subjects (plural!) by element id - in theory malformed
|
||||
* DOM might have same ids assigned to different elements and leaving
|
||||
* it to client/browser implementation which element to actually use.
|
||||
*
|
||||
* @param string $elementId
|
||||
* @return Subject[]
|
||||
*/
|
||||
public function findByElementId($elementId)
|
||||
{
|
||||
return array_filter(
|
||||
$this->subjects,
|
||||
function (Subject $subject) use ($elementId) {
|
||||
return $elementId === $subject->getElementId();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects elements having `id` attribute (those that can be referenced).
|
||||
*/
|
||||
protected function collectIdentifiedElements()
|
||||
{
|
||||
/** @var \DOMNodeList|\DOMElement[] $elements */
|
||||
$elements = $this->xPath->query('//*[@id]');
|
||||
foreach ($elements as $element) {
|
||||
$this->subjects[$element->getAttribute('id')] = new Subject($element, $this->useNestingLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes references from and to elements having `id` attribute concerning
|
||||
* their occurrence in `<use ... xlink:href="#identifier">` statements.
|
||||
*/
|
||||
protected function processReferences()
|
||||
{
|
||||
$useNodeName = $this->xPath->createNodeName('use');
|
||||
foreach ($this->subjects as $subject) {
|
||||
$useElements = $this->xPath->query(
|
||||
$useNodeName . '[@href or @xlink:href]',
|
||||
$subject->getElement()
|
||||
);
|
||||
|
||||
/** @var \DOMElement $useElement */
|
||||
foreach ($useElements as $useElement) {
|
||||
$useId = Helper::extractIdReferenceFromHref(
|
||||
Helper::getElementHref($useElement)
|
||||
);
|
||||
if ($useId === null || !isset($this->subjects[$useId])) {
|
||||
continue;
|
||||
}
|
||||
$subject->addUse($this->subjects[$useId]);
|
||||
$this->subjects[$useId]->addUsedIn($subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines and tags infinite loops.
|
||||
*/
|
||||
protected function determineInvalidSubjects()
|
||||
{
|
||||
foreach ($this->subjects as $subject) {
|
||||
|
||||
if (in_array($subject->getElement(), $this->elementsToRemove)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$useId = Helper::extractIdReferenceFromHref(
|
||||
Helper::getElementHref($subject->getElement())
|
||||
);
|
||||
|
||||
try {
|
||||
if ($useId === $subject->getElementId()) {
|
||||
$this->markSubjectAsInvalid($subject);
|
||||
} elseif ($subject->hasInfiniteLoop()) {
|
||||
$this->markSubjectAsInvalid($subject);
|
||||
}
|
||||
} catch (NestingException $e) {
|
||||
$this->elementsToRemove[] = $e->getElement();
|
||||
$this->markSubjectAsInvalid($subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the elements that caused a nesting exception.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getElementsToRemove() {
|
||||
return $this->elementsToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Subject is invalid for some reason, therefore we should
|
||||
* remove it and all it's child usages.
|
||||
*
|
||||
* @param Subject $subject
|
||||
*/
|
||||
protected function markSubjectAsInvalid(Subject $subject) {
|
||||
$this->elementsToRemove = array_merge(
|
||||
$this->elementsToRemove,
|
||||
$subject->clearInternalAndGetAffectedElements()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user