Passed
Push — master ( 1e5abb...e2dc80 )
by Tim
02:01 queued 12s
created

CustomAssertionTrait::nullOr()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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