Passed
Push — master ( d40d04...e849da )
by Tim
01:42
created

CustomAssertionTrait::validURL()   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;
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-z0-9-._~!$&\'()*+,;=:!\/?]|%[a-f0-9]{2})*$/i';
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
38
    /***********************************************************************************
39
     *  NOTE:  Custom assertions may be added below this line.                         *
40
     *         They SHOULD be marked as `private` to ensure the call is forced         *
41
     *          through __callStatic().                                                *
42
     *         Assertions marked `public` are called directly and will                 *
43
     *          not handle any custom exception passed to it.                          *
44
     ***********************************************************************************/
45
46
47
    /**
48
     * nullOr* for our custom assertions
49
     *
50
     * @param string $method
51
     * @param array $arguments
52
     * @return void
53
     */
54
    private static function nullOr(string $method, array $arguments): void
55
    {
56
        $value = reset($arguments);
57
        ($value === null) || call_user_func_array([static::class, $method], $arguments);
58
    }
59
60
61
    /**
62
     * @param string $value
63
     */
64
    private static function validDuration(string $value, string $message = ''): void
65
    {
66
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$duration_regex]]) === false) {
67
            throw new InvalidArgumentException(sprintf(
68
                $message ?: '\'%s\' is not a valid xs:duration',
69
                $value
70
            ));
71
        }
72
    }
73
74
75
    /**
76
     * Note: This test is not bullet-proof but prevents a string containing illegal characters
77
     * from being passed and ensures the string roughly follows the correct format for a Base64 encoded string
78
     *
79
     * @param string $value
80
     * @param string $message
81
     */
82
    private static function stringPlausibleBase64(string $value, string $message = ''): void
83
    {
84
        $result = true;
85
86
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$base64_regex]]) === false) {
87
            $result = false;
88
        } elseif (strlen($value) % 4 !== 0) {
89
            $result = false;
90
        } else {
91
            $decoded = base64_decode($value, true);
92
            if ($decoded === false) {
93
                $result = false;
94
            } elseif (base64_encode($decoded) !== $value) {
95
                $result = false;
96
            }
97
        }
98
99
        if ($result === false) {
100
            throw new InvalidArgumentException(sprintf(
101
                $message ?: '\'%s\' is not a valid Base64 encoded string',
102
                $value
103
            ));
104
        }
105
    }
106
107
108
    /**
109
     * @param string $value
110
     * @param string $message
111
     */
112
    private static function validDateTime(string $value, string $message = ''): void
113
    {
114
        if (
115
            DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value) === false &&
116
            DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value) === false
117
        ) {
118
            throw new InvalidArgumentException(sprintf(
119
                $message ?: '\'%s\' is not a valid DateTime',
120
                $value
121
            ));
122
        }
123
    }
124
125
126
    /**
127
     * @param string $value
128
     * @param string $message
129
     */
130
    private static function validDateTimeZulu(string $value, string $message = ''): void
131
    {
132
        $dateTime1 = DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value);
133
        $dateTime2 = DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value);
134
135
        $dateTime = $dateTime1 ?: $dateTime2;
0 ignored issues
show
introduced by
$dateTime1 is of type DateTimeImmutable, thus it always evaluated to true.
Loading history...
136
        if ($dateTime === false) {
0 ignored issues
show
introduced by
The condition $dateTime === false is always false.
Loading history...
137
            throw new InvalidArgumentException(sprintf(
138
                $message ?: '\'%s\' is not a valid DateTime',
139
                $value
140
            ));
141
        } elseif ($dateTime->getTimezone()->getName() !== 'Z') {
142
            throw new InvalidArgumentException(sprintf(
143
                $message ?: '\'%s\' is not a DateTime expressed in the UTC timezone using the \'Z\' timezone identifier.',
144
                $value
145
            ));
146
        }
147
    }
148
149
150
    /**
151
     * @param mixed $value
152
     * @param array $values
153
     * @param string $message
154
     */
155
    private static function notInArray($value, array $values, string $message = ''): void
156
    {
157
        if (in_array($value, $values, true)) {
158
            $callable = function ($value) {
159
                return self::valueToString($value);
160
            };
161
162
            throw new InvalidArgumentException(sprintf(
163
                $message ?: 'Expected none of: %2$s. Got: %s',
164
                self::valueToString($value),
165
                implode(', ', array_map($callable, $values)),
166
            ));
167
        }
168
    }
169
170
171
    /**
172
     * @param string $value
173
     * @param string $message
174
     */
175
    private static function validURN(string $value, string $message = ''): void
176
    {
177
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$urn_regex]]) === false) {
178
            throw new InvalidArgumentException(sprintf(
179
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
180
                $value
181
            ));
182
        }
183
    }
184
185
186
    /**
187
     * @param string $value
188
     * @param string $message
189
     */
190
    private static function validURL(string $value, string $message = ''): void
191
    {
192
        if (filter_var($value, FILTER_VALIDATE_URL) === false) {
193
            throw new InvalidArgumentException(sprintf(
194
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
195
                $value
196
            ));
197
        }
198
    }
199
200
201
    /**
202
     * @param string $value
203
     * @param string $message
204
     */
205
    private static function validURI(string $value, string $message = ''): void
206
    {
207
        if (
208
            filter_var($value, FILTER_VALIDATE_URL) === false &&
209
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$urn_regex]]) === false &&
210
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_same_document_regex]]) === false
211
        ) {
212
            throw new InvalidArgumentException(sprintf(
213
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
214
                $value
215
            ));
216
        }
217
    }
218
219
220
    /**
221
     * @param string $value
222
     * @param string $message
223
     */
224
    private static function validNCName(string $value, string $message = ''): void
225
    {
226
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
227
            throw new InvalidArgumentException(sprintf(
228
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
229
                $value
230
            ));
231
        }
232
    }
233
234
235
    /**
236
     * @param string $value
237
     * @param string $message
238
     */
239
    private static function validQName(string $value, string $message = ''): void
240
    {
241
        if (
242
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
243
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
244
        ) {
245
            throw new InvalidArgumentException(sprintf(
246
                $message ?: '\'%s\' is not a valid qualified name (QName)',
247
                $value
248
            ));
249
        }
250
    }
251
}
252