Skip to content

Commit

Permalink
TemplateChecks: added DisallowUnsupportedXSL, enabled by default
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshyPHP committed Mar 31, 2020
1 parent 2eb8146 commit 0390156
Show file tree
Hide file tree
Showing 6 changed files with 489 additions and 0 deletions.
23 changes: 23 additions & 0 deletions docs/testdox.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,29 @@ Disallow Unsafe Dynamic URL (s9e\TextFormatter\Tests\Configurator\TemplateChecks
[x] Disallowed: <a><xsl:attribute name="href"><xsl:apply-templates/></xsl:attribute>...</a>
[x] Disallowed: <a><xsl:attribute name="href"><xsl:for-each select="//*"><xsl:value-of select="@foo"/></xsl:for-each></xsl:attribute>...</a>

Disallow Unsupported XSL (s9e\TextFormatter\Tests\Configurator\TemplateChecks\DisallowUnsupportedXSL)
[x] Allowed: <b>...</b>
[x] Disallowed: <xsl:message>..</xsl:message>
[x] Allowed: <xsl:apply-templates/>
[x] Disallowed: <xsl:apply-templates mode="unsupported"/>
[x] Allowed: <xsl:copy-of select="@foo"/>
[x] Disallowed: <xsl:copy-of/>
[x] Allowed: <xsl:if test="@foo"/>
[x] Disallowed: <xsl:if/>
[x] Allowed: <xsl:value-of select="@foo"/>
[x] Disallowed: <xsl:value-of/>
[x] Allowed: <xsl:variable name="foo"/>
[x] Disallowed: <xsl:variable/>
[x] Allowed: <xsl:when test="@foo"/>
[x] Disallowed: <xsl:when/>
[x] Allowed: <xsl:value-of select="substring-after('foo()', &quot;bar()&quot;)"/>
[x] Allowed: <xsl:value-of select="1-number(@foo)"/>
[x] Disallowed: <xsl:value-of select="foo('bar')"/>
[x] Disallowed: <hr title="{foo()}"/>
[x] Allowed: <xsl:element name="{string(@name)}"/>
[x] Disallowed: <xsl:element name="{foo(@name)}"/>
[x] Allowed: <xsl:if test="foo and (bar or (baz mod (1 + 1)))"/>

Disallow XPath Function (s9e\TextFormatter\Tests\Configurator\TemplateChecks\DisallowXPathFunction)
[x] Disallowed: <xsl:value-of select="document(@foo)"/>
[x] Disallowed: <xsl:value-of select="php:function()"/>
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
<file>tests/Configurator/TemplateChecks/DisallowUnsafeDynamicCSSTest.php</file>
<file>tests/Configurator/TemplateChecks/DisallowUnsafeDynamicJSTest.php</file>
<file>tests/Configurator/TemplateChecks/DisallowUnsafeDynamicURLTest.php</file>
<file>tests/Configurator/TemplateChecks/DisallowUnsupportedXSLTest.php</file>
<file>tests/Configurator/TemplateChecks/DisallowXPathFunctionTest.php</file>
<file>tests/Configurator/TemplateChecks/RestrictFlashNetworkingTest.php</file>
<file>tests/Configurator/TemplateChecks/RestrictFlashScriptAccessTest.php</file>
Expand Down
3 changes: 3 additions & 0 deletions src/Configurator/TemplateChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ public function __construct()
$this->collection->append(new DisallowElementNS('http://icl.com/saxon', 'output'));
$this->collection->append(new DisallowXPathFunction('document'));
$this->collection->append(new RestrictFlashScriptAccess('sameDomain', true));

// Check for unsupported XSL last to allow for the more specialized checks to be run first
$this->collection->append('DisallowUnsupportedXSL');
}

/**
Expand Down
123 changes: 123 additions & 0 deletions src/Configurator/TemplateChecks/AbstractXSLSupportCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php declare(strict_types=1);

/**
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2020 The s9e authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateChecks;

use DOMElement;
use DOMXPath;
use RuntimeException;
use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
use s9e\TextFormatter\Configurator\Items\Tag;
use s9e\TextFormatter\Configurator\TemplateCheck;

abstract class AbstractXSLSupportCheck extends TemplateCheck
{
/**
* @var string[] List of supported XSL elements (local name only)
*/
protected $supportedElements = [];

/**
* @var string[] List of supported XPath functions
*/
protected $supportedFunctions = [];

/**
* @var string[] List of supported XPath operators
*/
protected $supportedOperators = ['and', 'div', 'mod', 'or'];

/**
* Check for elements not supported by the PHP renderer
*
* @param DOMElement $template <xsl:template/> node
* @param Tag $tag Tag this template belongs to
*/
public function check(DOMElement $template, Tag $tag): void
{
$this->checkXslElements($template);
$this->checkXPathExpressions($template);
}

/**
* Check given XPath expression
*/
protected function checkXPathExpression(string $expr): void
{
preg_match_all('("[^"]*+"|\'[^\']*+\'|((?:[a-z]++-)*+[a-z]++)(?=\\s*\\())', $expr, $m);
foreach (array_filter($m[1]) as $funcName)
{
if (!in_array($funcName, $this->supportedFunctions, true)
&& !in_array($funcName, $this->supportedOperators, true))
{
throw new RuntimeException('XPath function ' . $funcName . '() is not supported');
}
}
}

/**
* Check all XPath expressions in given template
*/
protected function checkXPathExpressions(DOMElement $template): void
{
foreach ($this->getXPathExpressions($template) as $expr)
{
$this->checkXPathExpression($expr);
}
}

/**
* Check all XSL elements in given template
*/
protected function checkXslElements(DOMElement $template): void
{
$xpath = new DOMXPath($template->ownerDocument);
$nodes = $xpath->query('/xsl:template//xsl:*');
foreach ($nodes as $node)
{
if (!in_array($node->localName, $this->supportedElements, true))
{
throw new RuntimeException('xsl:' . $node->localName . ' elements are not supported');
}

$methodName = 'checkXsl' . str_replace(' ', '', ucwords(str_replace('-', ' ', $node->localName))) . 'Element';
if (method_exists($this, $methodName))
{
$this->$methodName($node);
}
}
}

/**
* Return all XPath expressions in given template
*/
protected function getXPathExpressions(DOMElement $template): array
{
$exprs = [];
$xpath = new DOMXPath($template->ownerDocument);

$query = '//xsl:*/@name | //*[namespace-uri() != "' . self::XMLNS_XSL . '"]/@*[contains(., "{")]';
foreach ($xpath->query($query) as $attribute)
{
foreach (AVTHelper::parse($attribute->value) as [$type, $content])
{
if ($type === 'expression')
{
$exprs[] = $content;
}
}
}

$query = '//xsl:*/@select | //xsl:*/@test';
foreach ($xpath->query($query) as $attribute)
{
$exprs[] = $attribute->value;
}

return $exprs;
}
}
66 changes: 66 additions & 0 deletions src/Configurator/TemplateChecks/DisallowUnsupportedXSL.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php declare(strict_types=1);

/**
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2020 The s9e authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateChecks;

use DOMElement;
use RuntimeException;

class DisallowUnsupportedXSL extends AbstractXSLSupportCheck
{
protected $supportedElements = ['apply-templates', 'attribute', 'choose', 'comment', 'copy-of', 'element', 'for-each', 'if', 'number', 'otherwise', 'processing-instruction', 'sort', 'text', 'value-of', 'variable', 'when'];

protected $supportedFunctions = ['boolean', 'ceiling', 'concat', 'contains', 'count', 'current', 'document', 'element-available', 'false', 'floor', 'format-number', 'function-available', 'generate-id', 'id', 'key', 'lang', 'last', 'local-name', 'name', 'namespace-uri', 'normalize-space', 'not', 'number', 'position', 'round', 'starts-with', 'string', 'string-length', 'substring', 'substring-after', 'substring-before', 'sum', 'system-property', 'translate', 'true', 'unparsed-entity-uri'];

protected function checkXslApplyTemplatesElement(DOMElement $applyTemplates): void
{
if ($applyTemplates->hasAttribute('mode'))
{
throw new RuntimeException('xsl:apply-templates elements do not support the mode attribute');
}
}

protected function checkXslCopyOfElement(DOMElement $copyOf): void
{
if (!$copyOf->hasAttribute('select'))
{
throw new RuntimeException('xsl:copy-of elements require a select attribute');
}
}

protected function checkXslIfElement(DOMElement $if): void
{
if (!$if->hasAttribute('test'))
{
throw new RuntimeException('xsl:if elements require a test attribute');
}
}

protected function checkXslValueOfElement(DOMElement $valueOf): void
{
if (!$valueOf->hasAttribute('select'))
{
throw new RuntimeException('xsl:value-of elements require a select attribute');
}
}

protected function checkXslVariableElement(DOMElement $variable): void
{
if (!$variable->hasAttribute('name'))
{
throw new RuntimeException('xsl:variable elements require a name attribute');
}
}

protected function checkXslWhenElement(DOMElement $when): void
{
if (!$when->hasAttribute('test'))
{
throw new RuntimeException('xsl:when elements require a test attribute');
}
}
}
Loading

0 comments on commit 0390156

Please sign in to comment.