Completed
Push — master ( 6df7b6...2250c0 )
by Tim
24s queued 20s
created

XPathFilterTrait   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 58
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 3
eloc 14
c 4
b 0
f 0
dl 0
loc 58
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A validAllowedXPathFilter() 0 25 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XML\Assert;
6
7
use InvalidArgumentException;
8
use SimpleSAML\Assert\Assert as BaseAssert;
9
use SimpleSAML\XML\Constants as C;
10
use SimpleSAML\XML\Exception\RuntimeException;
11
use SimpleSAML\XML\Utils\XPathFilter;
12
13
use function sprintf;
14
15
/**
16
 * @package simplesamlphp/xml-common
17
 */
18
trait XPathFilterTrait
19
{
20
    /***********************************************************************************
21
     *  NOTE:  Custom assertions may be added below this line.                         *
22
     *         They SHOULD be marked as `private` to ensure the call is forced         *
23
     *          through __callStatic().                                                *
24
     *         Assertions marked `public` are called directly and will                 *
25
     *          not handle any custom exception passed to it.                          *
26
     ***********************************************************************************/
27
28
    /**
29
     * Check an XPath expression for allowed axes and functions
30
     * The goal is preventing DoS attacks by limiting the complexity of the XPath expression by only allowing
31
     * a select subset of functions and axes.
32
     * The check uses a list of allowed functions and axes, and throws an exception when an unknown function
33
     * or axis is found in the $xpathExpression.
34
     *
35
     * Limitations:
36
     * - The implementation is based on regular expressions, and does not employ an XPath 1.0 parser. It may not
37
     *   evaluate all possible valid XPath expressions correctly and cause either false positives for valid
38
     *   expressions or false negatives for invalid expressions.
39
     * - The check may still allow expressions that are not safe, I.e. expressions that consist of only
40
     *   functions and axes that are deemed "save", but that are still slow to evaluate. The time it takes to
41
     *   evaluate an XPath expression depends on the complexity of both the XPath expression and the XML document.
42
     *   This check, however, does not take the XML document into account, nor is it aware of the internals of the
43
     *   XPath processor that will evaluate the expression.
44
     * - The check was written with the XPath 1.0 syntax in mind, but should work equally well for XPath 2.0 and 3.0.
45
     *
46
     * @param string $xpathExpression
47
     * @param array<string> $allowedAxes
48
     * @param array<string> $allowedFunctions
49
     * @param string $message
50
     */
51
    public static function validAllowedXPathFilter(
52
        string $xpathExpression,
53
        array $allowedAxes = C::DEFAULT_ALLOWED_AXES,
54
        array $allowedFunctions = C::DEFAULT_ALLOWED_FUNCTIONS,
55
        string $message = '',
56
    ): void {
57
        BaseAssert::allString($allowedAxes);
58
        BaseAssert::allString($allowedFunctions);
59
        BaseAssert::maxLength(
60
            $xpathExpression,
61
            C::XPATH_FILTER_MAX_LENGTH,
62
            sprintf('XPath Filter exceeds the limit of 100 characters.'),
63
        );
64
65
        try {
66
            // First remove the contents of any string literals in the $xpath to prevent false positives
67
            $xpathWithoutStringLiterals = XPathFilter::removeStringContents($xpathExpression);
68
69
            // Then check that the xpath expression only contains allowed functions and axes, throws when it doesn't
70
            XPathFilter::filterXPathFunction($xpathWithoutStringLiterals, $allowedFunctions);
71
            XPathFilter::filterXPathAxis($xpathWithoutStringLiterals, $allowedAxes);
72
        } catch (RuntimeException $e) {
73
            throw new InvalidArgumentException(sprintf(
74
                $message ?: $e->getMessage(),
75
                $xpathExpression,
76
            ));
77
        }
78
    }
79
}
80