Passed
Push — master ( 084e1e...ecb5ad )
by Tim
01:20
created

CustomAssertionTrait::stringPlausibleBase64()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 21
rs 8.8333
cc 7
nc 10
nop 2
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 53 and the first side effect is on line 23.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Assert;
6
7
use DateTimeImmutable; // Requires ext-date
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
/**
0 ignored issues
show
Coding Style introduced by
The file-level docblock must follow the opening PHP tag in the file header
Loading history...
21
 * @package simplesamlphp/assert
22
 */
0 ignored issues
show
Coding Style introduced by
Header blocks must be separated by a single blank line
Loading history...
23
rait CustomAssertionTrait
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING on line 23 at column 5
Loading history...
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-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?|(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?))$#';
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
    private static string $uri_regex = '#[A-Za-z][A-Za-z0-9+\-.]*:(?:\/\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\?(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:\#(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?#';
38
39
    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])$/';
40
41
    /***********************************************************************************
42
     *  NOTE:  Custom assertions may be added below this line.                         *
43
     *         They SHOULD be marked as `private` to ensure the call is forced         *
44
     *          through __callStatic().                                                *
45
     *         Assertions marked `public` are called directly and will                 *
46
     *          not handle any custom exception passed to it.                          *
47
     ***********************************************************************************/
48
49
50
    /**
51
     * @param string $value
52
     */
53
    private static function validDuration(string $value, string $message = ''): void
54
    {
55
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$duration_regex]]) === false) {
56
            throw new InvalidArgumentException(sprintf(
57
                $message ?: '\'%s\' is not a valid xs:duration',
58
                $value
59
            ));
60
        }
61
    }
62
63
64
    /**
65
     * Note: This test is not bullet-proof but prevents a string containing illegal characters
66
     * from being passed and ensures the string roughly follows the correct format for a Base64 encoded string
67
     *
68
     * @param string $value
69
     * @param string $message
70
     */
71
    private static function stringPlausibleBase64(string $value, string $message = ''): void
72
    {
73
        $result = true;
74
75
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$base64_regex]]) === false) {
76
            $result = false;
77
        } elseif (strlen($value) % 4 !== 0) {
78
            $result = false;
79
        } else {
80
            $decoded = base64_decode($value, true);
81
            if ($decoded === false) {
82
                $result = false;
83
            } elseif (base64_encode($decoded) !== $value) {
84
                $result = false;
85
            }
86
        }
87
88
        if ($result === false) {
89
            throw new InvalidArgumentException(sprintf(
90
                $message ?: '\'%s\' is not a valid Base64 encoded string',
91
                $value
92
            ));
93
        }
94
    }
95
96
97
    /**
98
     * @param string $value
99
     * @param string $message
100
     */
101
    private static function validDateTime(string $value, string $message = ''): void
102
    {
103
        if (
104
            DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value) === false &&
105
            DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value) === false
106
        ) {
107
            throw new InvalidArgumentException(sprintf(
108
                $message ?: '\'%s\' is not a valid DateTime',
109
                $value
110
            ));
111
        }
112
    }
113
114
115
    /**
116
     * @param string $value
117
     * @param string $message
118
     */
119
    private static function validDateTimeZulu(string $value, string $message = ''): void
120
    {
121
        $dateTime1 = DateTimeImmutable::createFromFormat(DateTimeImmutable::ISO8601, $value);
122
        $dateTime2 = DateTimeImmutable::createFromFormat(DateTimeImmutable::RFC3339_EXTENDED, $value);
123
124
        $dateTime = $dateTime1 ?: $dateTime2;
125
        if ($dateTime === false) {
126
            throw new InvalidArgumentException(sprintf(
127
                $message ?: '\'%s\' is not a valid DateTime',
128
                $value
129
            ));
130
        } elseif ($dateTime->getTimezone()->getName() !== 'Z') {
131
            throw new InvalidArgumentException(sprintf(
132
                $message ?: '\'%s\' is not a DateTime expressed in the UTC timezone using the \'Z\' timezone identifier.',
133
                $value
134
            ));
135
        }
136
    }
137
138
139
    /**
140
     * @param mixed $value
141
     * @param array $values
142
     * @param string $message
143
     */
144
    private static function notInArray($value, array $values, string $message = ''): void
145
    {
146
        if (in_array($value, $values, true)) {
147
            $callable = /** @param mixed $val */function ($val) {
148
                return self::valueToString($val);
149
            };
150
151
            throw new InvalidArgumentException(sprintf(
152
                $message ?: 'Expected none of: %2$s. Got: %s',
153
                self::valueToString($value),
154
                implode(', ', array_map($callable, $values)),
155
            ));
156
        }
157
    }
158
159
160
    /**
161
     * @param string $value
162
     * @param string $message
163
     */
164
    private static function validURN(string $value, string $message = ''): void
165
    {
166
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$urn_regex]]) === false) {
167
            throw new InvalidArgumentException(sprintf(
168
                $message ?: '\'%s\' is not a valid RFC8141 compliant URN',
169
                $value
170
            ));
171
        }
172
    }
173
174
175
    /**
176
     * @param string $value
177
     * @param string $message
178
     */
179
    private static function validURL(string $value, string $message = ''): void
180
    {
181
        if (filter_var($value, FILTER_VALIDATE_URL) === false) {
182
            throw new InvalidArgumentException(sprintf(
183
                $message ?: '\'%s\' is not a valid RFC2396 compliant URL',
184
                $value
185
            ));
186
        }
187
    }
188
189
190
    /**
191
     * @param string $value
192
     * @param string $message
193
     */
194
    private static function validURI(string $value, string $message = ''): void
195
    {
196
        if (
197
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_regex]]) === false &&
198
            // We're very lenient here to accept DNS hostnames without a scheme
199
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$hostname_regex]]) === false &&
200
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$uri_same_document_regex]]) === false
201
        ) {
202
            throw new InvalidArgumentException(sprintf(
203
                $message ?: '\'%s\' is not a valid RFC3986 compliant URI',
204
                $value
205
            ));
206
        }
207
    }
208
209
210
    /**
211
     * @param string $value
212
     * @param string $message
213
     */
214
    private static function validNCName(string $value, string $message = ''): void
215
    {
216
        if (filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false) {
217
            throw new InvalidArgumentException(sprintf(
218
                $message ?: '\'%s\' is not a valid non-colonized name (NCName)',
219
                $value
220
            ));
221
        }
222
    }
223
224
225
    /**
226
     * @param string $value
227
     * @param string $message
228
     */
229
    private static function validQName(string $value, string $message = ''): void
230
    {
231
        if (
232
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$qname_regex]]) === false &&
233
            filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => self::$ncname_regex]]) === false
234
        ) {
235
            throw new InvalidArgumentException(sprintf(
236
                $message ?: '\'%s\' is not a valid qualified name (QName)',
237
                $value
238
            ));
239
        }
240
    }
241
}
242