Passed
Push — master ( aa98f8...bc80f1 )
by Tim
01:45
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 $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';
27
28
    /** @var string */
29
    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)?)?$/';
30
31
    /** @var string */
32
    private static string $qname_regex = '/^[a-zA-Z_][\w.-]*:[a-zA-Z_][\w.-]*$/';
33
34
    /** @var string */
35
    private static string $ncname_regex = '/^[a-zA-Z_][\w.-]*$/';
36
37
    /** @var string */
38
    private static string $base64_regex = '/^(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?$/i';
39
40
    /** @var string */
41
    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])$/';
42
43
    /***********************************************************************************
44
     *  NOTE:  Custom assertions may be added below this line.                         *
45
     *         They SHOULD be marked as `private` to ensure the call is forced         *
46
     *          through __callStatic().                                                *
47
     *         Assertions marked `public` are called directly and will                 *
48
     *          not handle any custom exception passed to it.                          *
49
     ***********************************************************************************/
50
51
52
    /**
53
     * @param string $value
54
     * @param string $message
55
     */
56
    private static function validDuration(string $value, string $message = ''): void
57
    {
58
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$duration_regex]]) === false) {
59
            throw new InvalidArgumentException(sprintf(
60
                $message ?: '\'%s\' is not a valid xs:duration',
61
                $value,
62
            ));
63
        }
64
    }
65
66
67
    /**
68
     * Note: This test is not bullet-proof but prevents a string containing illegal characters
69
     * from being passed and ensures the string roughly follows the correct format for a Base64 encoded string
70
     *
71
     * @param string $value
72
     * @param string $message
73
     */
74
    private static function stringPlausibleBase64(string $value, string $message = ''): void
75
    {
76
        $result = true;
77
78
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$base64_regex]]) === false) {
79
            $result = false;
80
        } elseif (strlen($value) % 4 !== 0) {
81
            $result = false;
82
        } else {
83
            $decoded = base64_decode($value, true);
84
            if (empty($decoded)) { // Invalid _or_ empty string
85
                $result = false;
86
            } elseif (base64_encode($decoded) !== $value) {
87
                $result = false;
88
            }
89
        }
90
91
        if ($result === false) {
92
            throw new InvalidArgumentException(sprintf(
93
                $message ?: '\'%s\' is not a valid Base64 encoded string',
94
                $value,
95
            ));
96
        }
97
    }
98
99
100
    /**
101
     * @param string $value
102
     * @param string $message
103
     */
104
    private static function validDateTime(string $value, string $message = ''): void
105
    {
106
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$datetime_regex]]) === false) {
107
            throw new InvalidArgumentException(sprintf(
108
                $message ?: '\'%s\' is not a valid xs:dateTime',
109
                $value,
110
            ));
111
        }
112
    }
113
114
115
    /**
116
     * @param mixed $value
117
     * @param array<mixed> $values
118
     * @param string $message
119
     */
120
    private static function notInArray($value, array $values, string $message = ''): void
121
    {
122
        if (in_array($value, $values, true)) {
123
            $callable = /** @param mixed $val */function ($val) {
124
                return self::valueToString($val);
125
            };
126
127
            throw new InvalidArgumentException(sprintf(
128
                $message ?: 'Expected none of: %2$s. Got: %s',
129
                self::valueToString($value),
130
                implode(', ', array_map($callable, $values)),
131
            ));
132
        }
133
    }
134
135
136
    /**
137
     * @param string $value
138
     * @param string $message
139
     */
140
    private static function validURN(string $value, string $message = ''): void
141
    {
142
        try {
143
            $uri = UriString::parse($value);
144
        } catch (SyntaxError $e) {
145
            throw new InvalidArgumentException(sprintf(
146
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
147
                $value,
148
            ));
149
        }
150
151
        if (
152
            $uri['scheme'] !== 'urn'
153
            || (($uri['scheme'] !== null) && $uri['path'] !== substr($value, strlen($uri['scheme']) + 1))
154
        ) {
155
            throw new InvalidArgumentException(sprintf(
156
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
157
                $value,
158
            ));
159
        }
160
    }
161
162
163
    /**
164
     * @param string $value
165
     * @param string $message
166
     */
167
    private static function validURL(string $value, string $message = ''): void
168
    {
169
        try {
170
            $uri = UriString::parse($value);
171
        } catch (SyntaxError $e) {
172
            throw new InvalidArgumentException(sprintf(
173
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
174
                $value,
175
            ));
176
        }
177
178
        if ($uri['scheme'] !== 'http' && $uri['scheme'] !== 'https') {
179
            throw new InvalidArgumentException(sprintf(
180
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
181
                $value,
182
            ));
183
        }
184
    }
185
186
187
    /**
188
     * @param string $value
189
     * @param string $message
190
     */
191
    private static function validURI(string $value, string $message = ''): void
192
    {
193
        try {
194
            UriString::parse($value);
195
        } catch (SyntaxError $e) {
196
            throw new InvalidArgumentException(sprintf(
197
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
198
                $value,
199
            ));
200
        }
201
    }
202
203
204
    /**
205
     * @param string $value
206
     * @param string $message
207
     */
208
    private static function validNCName(string $value, string $message = ''): void
209
    {
210
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
211
            throw new InvalidArgumentException(sprintf(
212
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
213
                $value,
214
            ));
215
        }
216
    }
217
218
219
    /**
220
     * @param string $value
221
     * @param string $message
222
     */
223
    private static function validQName(string $value, string $message = ''): void
224
    {
225
        if (
226
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
227
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
228
        ) {
229
            throw new InvalidArgumentException(sprintf(
230
                $message ?: '\'%s\' is not a valid qualified name (QName)',
231
                $value,
232
            ));
233
        }
234
    }
235
}
236