Passed
Push — master ( 57428a...7b7ef2 )
by Tim
01:44
created

CustomAssertionTrait::validDateTime()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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