Passed
Push — master ( 87b0e2...767723 )
by Tim
01:27
created

CustomAssertionTrait::validNCName()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 6
rs 10
cc 3
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Assert;
6
7
use DateTimeImmutable; // Requires ext-date
8
use InvalidArgumentException;
9
10
use function array_map;
11
use function base64_decode;
12
use function base64_encode;
13
use function call_user_func_array;
14
use function filter_var;
15
use function implode;
16
use function in_array;
17
use function reset;
18
use function sprintf;
19
20
/**
21
 * @package simplesamlphp/assert
22
 */
23
trait CustomAssertionTrait
24
{
25
    private static string $duration_regex = '/^(-?)P(?=.)((\d+)Y)?((\d+)M)?((\d+)D)?(T(?=.)((\d+)H)?((\d+)M)?(\d*(\.\d+)?S)?)?$/i';
26
27
    private static string $qname_regex = '/^[a-zA-Z_][\w.-]*:[a-zA-Z_][\w.-]*$/';
28
29
    private static string $ncname_regex = '/^[a-zA-Z_][\w.-]*$/';
30
31
    private static string $base64_regex = '/^(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?$/i';
32
33
    private static string $uri_same_document_regex = '#^(?:\#([A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?|(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?))$#';
34
35
    private static string $urn_regex = '/\A(?i:urn:(?!urn:)(?<nid>[a-z0-9][a-z0-9-]{1,31}):(?<nss>(?:[-a-z0-9()+,.:=@;$_!*\'&~\/]|%[0-9a-f]{2})+)(?:\?\+(?<rcomponent>.*?))?(?:\?=(?<qcomponent>.*?))?(?:#(?<fcomponent>.*?))?)\z/';
36
37
    private static string $uri_regex = '#[A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?#';
38
39
    private static string $hostname_regex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/';
40
41
    /***********************************************************************************
42
     *  NOTE:  Custom assertions may be added below this line.                         *
43
     *         They SHOULD be marked as `private` to ensure the call is forced         *
44
     *          through __callStatic().                                                *
45
     *         Assertions marked `public` are called directly and will                 *
46
     *          not handle any custom exception passed to it.                          *
47
     ***********************************************************************************/
48
49
    private function alwaysReturnsTrue():true {}
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Assert\true 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...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return SimpleSAML\Assert\true. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
50
51
    /**
52
     * @param string $value
53
     */
54
    private static function validDuration(string $value, string $message = ''): void
55
    {
56
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$duration_regex]]) === false) {
57
            throw new InvalidArgumentException(sprintf(
58
                $message ?: '\'%s\' is not a valid xs:duration',
59
                $value
60
            ));
61
        }
62
    }
63
64
65
    /**
66
     * Note: This test is not bullet-proof but prevents a string containing illegal characters
67
     * from being passed and ensures the string roughly follows the correct format for a Base64 encoded string
68
     *
69
     * @param string $value
70
     * @param string $message
71
     */
72
    private static function stringPlausibleBase64(string $value, string $message = ''): void
73
    {
74
        $result = true;
75
76
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$base64_regex]]) === false) {
77
            $result = false;
78
        } elseif (strlen($value) % 4 !== 0) {
79
            $result = false;
80
        } else {
81
            $decoded = base64_decode($value, true);
82
            if ($decoded === false) {
83
                $result = false;
84
            } elseif (base64_encode($decoded) !== $value) {
85
                $result = false;
86
            }
87
        }
88
89
        if ($result === false) {
90
            throw new InvalidArgumentException(sprintf(
91
                $message ?: '\'%s\' is not a valid Base64 encoded string',
92
                $value
93
            ));
94
        }
95
    }
96
97
98
    /**
99
     * @param string $value
100
     * @param string $message
101
     */
102
    private static function validDateTime(string $value, string $message = ''): void
103
    {
104
        if (
105
            DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value) === false &&
106
            DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value) === false
107
        ) {
108
            throw new InvalidArgumentException(sprintf(
109
                $message ?: '\'%s\' is not a valid DateTime',
110
                $value
111
            ));
112
        }
113
    }
114
115
116
    /**
117
     * @param string $value
118
     * @param string $message
119
     */
120
    private static function validDateTimeZulu(string $value, string $message = ''): void
121
    {
122
        $dateTime1 = DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value);
123
        $dateTime2 = DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value);
124
125
        $dateTime = $dateTime1 ?: $dateTime2;
0 ignored issues
show
introduced by
$dateTime1 is of type DateTimeImmutable, thus it always evaluated to true.
Loading history...
126
        if ($dateTime === false) {
0 ignored issues
show
introduced by
The condition $dateTime === false is always false.
Loading history...
127
            throw new InvalidArgumentException(sprintf(
128
                $message ?: '\'%s\' is not a valid DateTime',
129
                $value
130
            ));
131
        } elseif ($dateTime->getTimezone()->getName() !== 'Z') {
132
            throw new InvalidArgumentException(sprintf(
133
                $message ?: '\'%s\' is not a DateTime expressed in the UTC timezone using the \'Z\' timezone identifier.',
134
                $value
135
            ));
136
        }
137
    }
138
139
140
    /**
141
     * @param mixed $value
142
     * @param array $values
143
     * @param string $message
144
     */
145
    private static function notInArray($value, array $values, string $message = ''): void
146
    {
147
        if (in_array($value, $values, true)) {
148
            $callable = /** @param mixed $val */function ($val) {
149
                return self::valueToString($val);
150
            };
151
152
            throw new InvalidArgumentException(sprintf(
153
                $message ?: 'Expected none of: %2$s. Got: %s',
154
                self::valueToString($value),
155
                implode(', ', array_map($callable, $values)),
156
            ));
157
        }
158
    }
159
160
161
    /**
162
     * @param string $value
163
     * @param string $message
164
     */
165
    private static function validURN(string $value, string $message = ''): void
166
    {
167
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$urn_regex]]) === false) {
168
            throw new InvalidArgumentException(sprintf(
169
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
170
                $value
171
            ));
172
        }
173
    }
174
175
176
    /**
177
     * @param string $value
178
     * @param string $message
179
     */
180
    private static function validURL(string $value, string $message = ''): void
181
    {
182
        if (filter_var($value, FILTER_VALIDATE_URL) === false) {
183
            throw new InvalidArgumentException(sprintf(
184
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
185
                $value
186
            ));
187
        }
188
    }
189
190
191
    /**
192
     * @param string $value
193
     * @param string $message
194
     */
195
    private static function validURI(string $value, string $message = ''): void
196
    {
197
        if (
198
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_regex]]) === false &&
199
            // We're very lenient here to accept DNS hostnames without a scheme
200
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$hostname_regex]]) === false &&
201
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_same_document_regex]]) === false
202
        ) {
203
            throw new InvalidArgumentException(sprintf(
204
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
205
                $value
206
            ));
207
        }
208
    }
209
210
211
    /**
212
     * @param string $value
213
     * @param string $message
214
     */
215
    private static function validNCName(string $value, string $message = ''): void
216
    {
217
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
218
            throw new InvalidArgumentException(sprintf(
219
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
220
                $value
221
            ));
222
        }
223
    }
224
225
226
    /**
227
     * @param string $value
228
     * @param string $message
229
     */
230
    private static function validQName(string $value, string $message = ''): void
231
    {
232
        if (
233
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
234
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
235
        ) {
236
            throw new InvalidArgumentException(sprintf(
237
                $message ?: '\'%s\' is not a valid qualified name (QName)',
238
                $value
239
            ));
240
        }
241
    }
242
}
243