XPathFilter::filterXPathFunction()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 2
dl 0
loc 41
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XPath;
6
7
use SimpleSAML\XML\Assert\Assert;
8
use SimpleSAML\XPath\Constants as C;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XPath\Constants was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use SimpleSAML\XPath\Exception\AxisNotAllowedException;
10
use SimpleSAML\XPath\Exception\FunctionNotAllowedException;
11
use SimpleSAML\XPath\Exception\RuntimeException;
12
13
use function preg_match_all;
14
use function preg_replace;
15
16
/**
17
 * XPathFilter helper functions for the XML library.
18
 *
19
 * @package simplesamlphp/xml-common
20
 */
21
class XPathFilter
22
{
23
    /**
24
     * Remove the content from all single or double-quoted strings in $input, leaving only quotes.
25
     *
26
     * @param string $input
27
     * @throws \SimpleSAML\XPath\Exception\RuntimeException
28
     */
29
    public static function removeStringContents(string $input): string
30
    {
31
        /**
32
         * This regex should not be vulnerable to a ReDOS, because it uses possessive quantifiers
33
         * that prevent backtracking.
34
         *
35
         * @see https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
36
         *
37
         * Use possessive quantifiers (i.e. *+ and ++ instead of * and + respectively) to prevent backtracking.
38
         *
39
         * '/(["\'])(?:(?!\1).)*+\1/'
40
         *  (["\'])  # Match a single or double quote and capture it in group 1
41
         *  (?:      # Start a non-capturing group
42
         *    (?!    # Negative lookahead
43
         *      \1   # Match the same quote as in group 1
44
         *    )      # End of negative lookahead
45
         *    .      # Match any character (that is not a quote, because of the negative lookahead)
46
         *  )*+      # Repeat the non-capturing group zero or more times, possessively
47
         *  \1       # Match the same quote as in group 1
48
         */
49
        $res = preg_replace(
50
            '/(["\'])(?:(?!\\1).)*+\\1/',
51
            "\\1\\1",   // Replace the content with two of the quotes that were matched
52
            $input,
53
        );
54
55
        if (null === $res) {
56
            throw new RuntimeException("Error in preg_replace");
57
        }
58
59
        return $res;
60
    }
61
62
63
    /**
64
     * Check if the $xpathExpression uses an XPath function that is not in the list of allowed functions
65
     *
66
     * @param string $xpathExpression the expression to check. Should be a valid xpath expression
67
     * @param string[] $allowedFunctions array of string with a list of allowed function names
68
     * @throws \SimpleSAML\XPath\Exception\RuntimeException
69
     */
70
    public static function filterXPathFunction(
71
        string $xpathExpression,
72
        array $allowedFunctions = C::DEFAULT_ALLOWED_FUNCTIONS,
73
    ): void {
74
        /**
75
         * Look for the function specifier '(' and look for a function name before it.
76
         * Ignoring whitespace before the '(' and the function name.
77
         * All functions must match a string on a list of allowed function names
78
         */
79
        $matches = [];
80
        preg_match_all(
81
            /**
82
             * Function names are lower-case alpha (i.e. [a-z]) and can contain one or more hyphens,
83
             * but cannot start or end with a hyphen. To match this, we start with matching one or more
84
             * lower-case alpha characters, followed by zero or more atomic groups that start with a hyphen
85
             * and then match one or more lower-case alpha characters. This ensures that the function name
86
             * cannot start or end with a hyphen, but can contain one or more hyphens.
87
             * More than one consecutive hyphen does not match.
88
             *
89
             * Use possessive quantifiers (i.e. *+ and ++ instead of * and + respectively) to prevent backtracking
90
             * and thus prevent a ReDOS.
91
92
             * '/([a-z]++(?>-[a-z]++)*+)\s*+\(/'
93
             * (           # Start a capturing group
94
             *   [a-z]++   # Match one or more lower-case alpha characters
95
             *   (?>       # Start an atomic group (no capturing)
96
             *     -       # Match a hyphen
97
             *     [a-z]++ # Match one or more lower-case alpha characters, possessively
98
             *   )*+       # Repeat the atomic group zero or more times,
99
             * )           # End of the capturing group
100
             * \s*+        # Match zero or more whitespace characters, possessively
101
             * \(          # Match an opening parenthesis
102
            */
103
104
            '/([a-z]++(?>-[a-z]++)*+)\\s*+\\(/',
105
            $xpathExpression,
106
            $matches,
107
        );
108
109
        // Check that all the function names we found are in the list of allowed function names
110
        Assert::allOneOf($matches[1], $allowedFunctions, "Invalid function: %s", FunctionNotAllowedException::class);
111
    }
112
113
114
    /**
115
     * Check if the $xpathExpression uses an XPath axis that is not in the list of allowed axes
116
     *
117
     * @param string $xpathExpression the expression to check. Should be a valid xpath expression
118
     * @param string[] $allowedAxes array of string with a list of allowed axes names
119
     * @throws \SimpleSAML\XPath\Exception\RuntimeException
120
     */
121
    public static function filterXPathAxis(string $xpathExpression, array $allowedAxes): void
122
    {
123
        /**
124
         * Look for the axis specifier '::' and look for a function name before it.
125
         * Ignoring whitespace before the '::' and the axis name.
126
         * All axes must match a string on a list of allowed axis names
127
         */
128
        $matches = [];
129
        preg_match_all(
130
            /**
131
             * We use the same rules for matching Axis names as we do for function names.
132
             * The only difference is that we match the '::' instead of the '('
133
             * so everything that was said about the regular expression for function names
134
             * applies here as well.
135
             *
136
             * Use possessive quantifiers (i.e. *+ and ++ instead of * and + respectively) to prevent backtracking
137
             * and thus prevent a ReDOS.
138
             *
139
             * '/([a-z]++(?>-[a-z]++)*+)\s*+::'
140
             * (           # Start a capturing group
141
             *   [a-z]++   # Match one or more lower-case alpha characters
142
             *   (?>       # Start an atomic group (no capturing)
143
             *     -       # Match a hyphen
144
             *     [a-z]++ # Match one or more lower-case alpha characters, possessively
145
             *   )*+       # Repeat the atomic group zero or more times,
146
             * )           # End of the capturing group
147
             * \s*+        # Match zero or more whitespace characters, possessively
148
             * \(          # Match an opening parenthesis
149
            */
150
151
            '/([a-z]++(?>-[a-z]++)*+)\\s*+::/',
152
            $xpathExpression,
153
            $matches,
154
        );
155
156
        // Check that all the axes names we found are in the list of allowed axes names
157
        Assert::allInArray($matches[1], $allowedAxes, "Invalid axis: %s", AxisNotAllowedException::class);
158
    }
159
}
160