Passed
Push — master ( 4e805d...057a9e )
by Tim
04:14 queued 02:32
created

CustomAssertionTrait::validURN()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 18
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Assert;
6
7
use InvalidArgumentException;
8
use League\Uri\Exceptions\SyntaxError;
9
use League\Uri\UriString;
10
11
use function array_map;
12
use function base64_decode;
13
use function base64_encode;
14
use function filter_var;
15
use function implode;
16
use function in_array;
17
use function sprintf;
18
use function substr;
19
20
/**
21
 * @package simplesamlphp/assert
22
 */
23
trait CustomAssertionTrait
24
{
25
    /** @var string */
26
    private static string $hexbin_regex = '/^([0-9a-fA-F]{2})+$/D';
27
28
    /** @var string */
29
    private static string $nmtoken_regex = '/^[\w.:-]+$/Du';
30
31
    /** @var string */
32
    private static string $nmtokens_regex = '/^([\w.:-]+)([\s][\w.:-]+)*$/Du';
33
34
    /** @var string */
35
    private static string $datetime_regex = '/-?[0-9]{4}-(((0(1|3|5|7|8)|1(0|2))-(0[1-9]|(1|2)[0-9]|3[0-1]))|((0(4|6|9)|11)-(0[1-9]|(1|2)[0-9]|30))|(02-(0[1-9]|(1|2)[0-9])))T([0-1][0-9]|2[0-4]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])(\.[0-999])?((\+|-)([0-1][0-9]|2[0-4]):(0[0-9]|[1-5][0-9])|Z)?/Di';
36
37
    /** @var string */
38
    private static string $duration_regex = '/^([-+]?)P(?!$)(?:(?<years>\d+(?:[\.\,]\d+)?)Y)?(?:(?<months>\d+(?:[\.\,]\d+)?)M)?(?:(?<weeks>\d+(?:[\.\,]\d+)?)W)?(?:(?<days>\d+(?:[\.\,]\d+)?)D)?(T(?=\d)(?:(?<hours>\d+(?:[\.\,]\d+)?)H)?(?:(?<minutes>\d+(?:[\.\,]\d+)?)M)?(?:(?<seconds>\d+(?:[\.\,]\d+)?)S)?)?$/D';
39
40
    /** @var string */
41
    private static string $qname_regex = '/^[a-zA-Z_][\w.-]*:[a-zA-Z_][\w.-]*$/D';
42
43
    /** @var string */
44
    private static string $ncname_regex = '/^[a-zA-Z_][\w.-]*$/D';
45
46
    /** @var string */
47
    private static string $base64_regex = '/^(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?$/i';
48
49
    /***********************************************************************************
50
     *  NOTE:  Custom assertions may be added below this line.                         *
51
     *         They SHOULD be marked as `private` to ensure the call is forced         *
52
     *          through __callStatic().                                                *
53
     *         Assertions marked `public` are called directly and will                 *
54
     *          not handle any custom exception passed to it.                          *
55
     ***********************************************************************************/
56
57
58
    /**
59
     * @param string $value
60
     * @param string $message
61
     */
62
    private static function validNMToken(string $value, string $message = ''): void
63
    {
64
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$nmtoken_regex]]) === false) {
65
            throw new InvalidArgumentException(sprintf(
66
                $message ?: '\'%s\' is not a valid xs:NMTOKEN',
67
                $value,
68
            ));
69
        }
70
    }
71
72
73
    /**
74
     * @param string $value
75
     * @param string $message
76
     */
77
    private static function validNMTokens(string $value, string $message = ''): void
78
    {
79
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$nmtokens_regex]]) === false) {
80
            throw new InvalidArgumentException(sprintf(
81
                $message ?: '\'%s\' is not a valid xs:NMTOKENS',
82
                $value,
83
            ));
84
        }
85
    }
86
87
88
    /**
89
     * @param string $value
90
     * @param string $message
91
     */
92
    private static function validDuration(string $value, string $message = ''): void
93
    {
94
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$duration_regex]]) === false) {
95
            throw new InvalidArgumentException(sprintf(
96
                $message ?: '\'%s\' is not a valid xs:duration',
97
                $value,
98
            ));
99
        }
100
    }
101
102
103
    /**
104
     * Note: This test is not bullet-proof but prevents a string containing illegal characters
105
     * from being passed and ensures the string roughly follows the correct format for a Base64 encoded string
106
     *
107
     * @param string $value
108
     * @param string $message
109
     */
110
    private static function stringPlausibleBase64(string $value, string $message = ''): void
111
    {
112
        $result = true;
113
114
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$base64_regex]]) === false) {
115
            $result = false;
116
        } elseif (strlen($value) % 4 !== 0) {
117
            $result = false;
118
        } else {
119
            $decoded = base64_decode($value, true);
120
            if (empty($decoded)) { // Invalid _or_ empty string
121
                $result = false;
122
            } elseif (base64_encode($decoded) !== $value) {
123
                $result = false;
124
            }
125
        }
126
127
        if ($result === false) {
128
            throw new InvalidArgumentException(sprintf(
129
                $message ?: '\'%s\' is not a valid Base64 encoded string',
130
                $value,
131
            ));
132
        }
133
    }
134
135
136
    /**
137
     * @param string $value
138
     * @param string $message
139
     */
140
    private static function validHexBinary(string $value, string $message = ''): void
141
    {
142
        $result = true;
143
144
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$hexbin_regex]]) === false) {
145
            $result = false;
146
        }
147
148
        if ($result === false) {
149
            throw new InvalidArgumentException(sprintf(
150
                $message ?: '\'%s\' is not a valid hexBinary string',
151
                $value,
152
            ));
153
        }
154
    }
155
156
157
    /**
158
     * @param string $value
159
     * @param string $message
160
     */
161
    private static function validDateTime(string $value, string $message = ''): void
162
    {
163
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$datetime_regex]]) === false) {
164
            throw new InvalidArgumentException(sprintf(
165
                $message ?: '\'%s\' is not a valid xs:dateTime',
166
                $value,
167
            ));
168
        }
169
    }
170
171
172
    /**
173
     * @param mixed $value
174
     * @param array<mixed> $values
175
     * @param string $message
176
     */
177
    private static function notInArray($value, array $values, string $message = ''): void
178
    {
179
        if (in_array($value, $values, true)) {
180
            $callable = /** @param mixed $val */function ($val) {
181
                return self::valueToString($val);
182
            };
183
184
            throw new InvalidArgumentException(sprintf(
185
                $message ?: 'Expected none of: %2$s. Got: %s',
186
                self::valueToString($value),
187
                implode(', ', array_map($callable, $values)),
188
            ));
189
        }
190
    }
191
192
193
    /**
194
     * @param string $value
195
     * @param string $message
196
     */
197
    private static function validURN(string $value, string $message = ''): void
198
    {
199
        try {
200
            $uri = UriString::parse($value);
201
        } catch (SyntaxError $e) {
202
            throw new InvalidArgumentException(sprintf(
203
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
204
                $value,
205
            ));
206
        }
207
208
        if (
209
            $uri['scheme'] !== 'urn'
210
            || (($uri['scheme'] !== null) && $uri['path'] !== substr($value, strlen($uri['scheme']) + 1))
211
        ) {
212
            throw new InvalidArgumentException(sprintf(
213
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
214
                $value,
215
            ));
216
        }
217
    }
218
219
220
    /**
221
     * @param string $value
222
     * @param string $message
223
     */
224
    private static function validURL(string $value, string $message = ''): void
225
    {
226
        try {
227
            $uri = UriString::parse($value);
228
        } catch (SyntaxError $e) {
229
            throw new InvalidArgumentException(sprintf(
230
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
231
                $value,
232
            ));
233
        }
234
235
        if ($uri['scheme'] !== 'http' && $uri['scheme'] !== 'https') {
236
            throw new InvalidArgumentException(sprintf(
237
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
238
                $value,
239
            ));
240
        }
241
    }
242
243
244
    /**
245
     * @param string $value
246
     * @param string $message
247
     */
248
    private static function validURI(string $value, string $message = ''): void
249
    {
250
        try {
251
            UriString::parse($value);
252
        } catch (SyntaxError $e) {
253
            throw new InvalidArgumentException(sprintf(
254
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
255
                $value,
256
            ));
257
        }
258
    }
259
260
261
    /**
262
     * @param string $value
263
     * @param string $message
264
     */
265
    private static function validNCName(string $value, string $message = ''): void
266
    {
267
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
268
            throw new InvalidArgumentException(sprintf(
269
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
270
                $value,
271
            ));
272
        }
273
    }
274
275
276
    /**
277
     * @param string $value
278
     * @param string $message
279
     */
280
    private static function validQName(string $value, string $message = ''): void
281
    {
282
        if (
283
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
284
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
285
        ) {
286
            throw new InvalidArgumentException(sprintf(
287
                $message ?: '\'%s\' is not a valid qualified name (QName)',
288
                $value,
289
            ));
290
        }
291
    }
292
}
293