Passed
Push — master ( 384be0...cf9465 )
by Tim
12:30
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 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 $nmtoken_regex = '/^[\w.:-]+$/u';
27
28
    /** @var string */
29
    private static string $nmtokens_regex = '/^([\w.:-]+)([\s][\w.:-]+)*$/u';
30
31
    /** @var string */
32
    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)?/i';
33
34
    /** @var string */
35
    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)?)?$/';
36
37
    /** @var string */
38
    private static string $qname_regex = '/^[a-zA-Z_][\w.-]*:[a-zA-Z_][\w.-]*$/';
39
40
    /** @var string */
41
    private static string $ncname_regex = '/^[a-zA-Z_][\w.-]*$/';
42
43
    /** @var string */
44
    private static string $base64_regex = '/^(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?$/i';
45
46
    /** @var string */
47
    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])$/';
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 validDateTime(string $value, string $message = ''): void
141
    {
142
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$datetime_regex]]) === false) {
143
            throw new InvalidArgumentException(sprintf(
144
                $message ?: '\'%s\' is not a valid xs:dateTime',
145
                $value,
146
            ));
147
        }
148
    }
149
150
151
    /**
152
     * @param mixed $value
153
     * @param array<mixed> $values
154
     * @param string $message
155
     */
156
    private static function notInArray($value, array $values, string $message = ''): void
157
    {
158
        if (in_array($value, $values, true)) {
159
            $callable = /** @param mixed $val */function ($val) {
160
                return self::valueToString($val);
161
            };
162
163
            throw new InvalidArgumentException(sprintf(
164
                $message ?: 'Expected none of: %2$s. Got: %s',
165
                self::valueToString($value),
166
                implode(', ', array_map($callable, $values)),
167
            ));
168
        }
169
    }
170
171
172
    /**
173
     * @param string $value
174
     * @param string $message
175
     */
176
    private static function validURN(string $value, string $message = ''): void
177
    {
178
        try {
179
            $uri = UriString::parse($value);
180
        } catch (SyntaxError $e) {
181
            throw new InvalidArgumentException(sprintf(
182
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
183
                $value,
184
            ));
185
        }
186
187
        if (
188
            $uri['scheme'] !== 'urn'
189
            || (($uri['scheme'] !== null) && $uri['path'] !== substr($value, strlen($uri['scheme']) + 1))
190
        ) {
191
            throw new InvalidArgumentException(sprintf(
192
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
193
                $value,
194
            ));
195
        }
196
    }
197
198
199
    /**
200
     * @param string $value
201
     * @param string $message
202
     */
203
    private static function validURL(string $value, string $message = ''): void
204
    {
205
        try {
206
            $uri = UriString::parse($value);
207
        } catch (SyntaxError $e) {
208
            throw new InvalidArgumentException(sprintf(
209
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
210
                $value,
211
            ));
212
        }
213
214
        if ($uri['scheme'] !== 'http' && $uri['scheme'] !== 'https') {
215
            throw new InvalidArgumentException(sprintf(
216
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
217
                $value,
218
            ));
219
        }
220
    }
221
222
223
    /**
224
     * @param string $value
225
     * @param string $message
226
     */
227
    private static function validURI(string $value, string $message = ''): void
228
    {
229
        try {
230
            UriString::parse($value);
231
        } catch (SyntaxError $e) {
232
            throw new InvalidArgumentException(sprintf(
233
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
234
                $value,
235
            ));
236
        }
237
    }
238
239
240
    /**
241
     * @param string $value
242
     * @param string $message
243
     */
244
    private static function validNCName(string $value, string $message = ''): void
245
    {
246
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
247
            throw new InvalidArgumentException(sprintf(
248
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
249
                $value,
250
            ));
251
        }
252
    }
253
254
255
    /**
256
     * @param string $value
257
     * @param string $message
258
     */
259
    private static function validQName(string $value, string $message = ''): void
260
    {
261
        if (
262
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
263
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
264
        ) {
265
            throw new InvalidArgumentException(sprintf(
266
                $message ?: '\'%s\' is not a valid qualified name (QName)',
267
                $value,
268
            ));
269
        }
270
    }
271
}
272