Passed
Push — main ( 7a8937...9ed332 )
by Greg
10:41 queued 03:14
created

Validator::isLocalUrl()   B

Complexity

Conditions 10
Paths 1

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 1
nop 1
dl 0
loc 34
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2021 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees;
21
22
use Closure;
23
use Fisharebest\Webtrees\Http\Exceptions\HttpBadRequestException;
24
use LogicException;
25
use Psr\Http\Message\ServerRequestInterface;
26
27
use function array_reduce;
28
use function ctype_digit;
29
use function gettype;
30
use function is_array;
31
use function is_int;
32
use function is_string;
33
use function parse_url;
34
use function preg_match;
35
use function str_starts_with;
36
37
/**
38
 * Validate a parameter from an HTTP request
39
 */
40
class Validator
41
{
42
    /** @var array<string|array> */
43
    private array $parameters;
44
45
    /** @var array<Closure> */
46
    private array $rules = [];
47
48
    /**
49
     * @param array<string|array> $parameters
50
     */
51
    public function __construct(array $parameters)
52
    {
53
        $this->parameters = $parameters;
54
    }
55
56
    /**
57
     * @param ServerRequestInterface $request
58
     *
59
     * @return self
60
     */
61
    public static function parsedBody(ServerRequestInterface $request): self
62
    {
63
        return new self((array) $request->getParsedBody());
64
    }
65
66
    /**
67
     * @param ServerRequestInterface $request
68
     *
69
     * @return self
70
     */
71
    public static function queryParams(ServerRequestInterface $request): self
72
    {
73
        return new self($request->getQueryParams());
74
    }
75
76
    /**
77
     * @param int $minimum
78
     * @param int $maximum
79
     *
80
     * @return self
81
     */
82
    public function isBetween(int $minimum, int $maximum): self
83
    {
84
        $this->rules[] = static function ($value) use ($minimum, $maximum): ?int {
85
            if (is_int($value)) {
86
                if ($value >= $minimum && $value <= $maximum) {
87
                    return $value;
88
                }
89
90
                return null;
91
            }
92
93
            if ($value === null) {
94
                return null;
95
            }
96
97
            throw new LogicException(__METHOD__ . ' does not accept ' . gettype($value));
98
        };
99
100
        return $this;
101
    }
102
103
    /**
104
     * @param string $base_url
105
     *
106
     * @return $this
107
     */
108
    public function isLocalUrl(string $base_url): self
109
    {
110
        $this->rules[] = static function ($value) use ($base_url): ?string {
111
            if (is_string($value)) {
112
                $value_info    = parse_url($value);
113
                $base_url_info = parse_url($base_url);
114
115
                if (!is_array($base_url_info)) {
1 ignored issue
show
introduced by
The condition is_array($base_url_info) is always true.
Loading history...
116
                    throw new LogicException(__METHOD__ . ' needs a valid URL');
117
                }
118
119
                if (is_array($value_info)) {
1 ignored issue
show
introduced by
The condition is_array($value_info) is always true.
Loading history...
120
                    $scheme_ok = ($value_info['scheme'] ?? 'http') === ($base_url_info['scheme'] ?? 'http');
121
                    $host_ok   = ($value_info['host'] ?? '') === ($base_url_info['host'] ?? '');
122
                    $port_ok   = ($value_info['port'] ?? '') === ($base_url_info['port'] ?? '');
123
                    $user_ok   = ($value_info['user'] ?? '') === ($base_url_info['user'] ?? '');
124
                    $path_ok   = str_starts_with($value_info['path'] ?? '/', $base_url_info['path'] ?? '/');
125
126
                    if ($scheme_ok && $host_ok && $port_ok && $user_ok && $path_ok) {
127
                        return $value;
128
                    }
129
                }
130
131
                return null;
132
            }
133
134
            if ($value === null) {
135
                return null;
136
            }
137
138
            throw new LogicException(__METHOD__ . ' does not accept ' . gettype($value));
139
        };
140
141
        return $this;
142
    }
143
144
    /**
145
     * @return $this
146
     */
147
    public function isXref(): self
148
    {
149
        $this->rules[] = static function ($value) {
150
            if (is_string($value)) {
151
                if (preg_match('/^' . Gedcom::REGEX_XREF . '$/', $value)) {
152
                    return $value;
153
                }
154
155
                return null;
156
            }
157
158
            if (is_array($value)) {
159
                foreach ($value as $item) {
160
                    if (!preg_match('/^' . Gedcom::REGEX_XREF . '$/', $item)) {
161
                        return null;
162
                    }
163
                }
164
165
                return $value;
166
            }
167
168
            if ($value === null) {
169
                return null;
170
            }
171
172
            throw new LogicException(__METHOD__ . ' does not accept ' . gettype($value));
173
        };
174
175
        return $this;
176
    }
177
178
    /**
179
     * @param string $parameter
180
     *
181
     * @return array<string>
182
     */
183
    public function array(string $parameter): array
184
    {
185
        $value = $this->parameters[$parameter] ?? null;
186
187
        if (!is_array($value)) {
188
            $value = null;
189
        }
190
191
        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
192
    }
193
194
    /**
195
     * @param string $parameter
196
     *
197
     * @return int|null
198
     */
199
    public function integer(string $parameter): ?int
200
    {
201
        $value = $this->parameters[$parameter] ?? null;
202
203
        if (is_string($value) && ctype_digit($value)) {
204
            $value = (int) $value;
205
        } else {
206
            $value = null;
207
        }
208
209
        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
210
    }
211
212
    /**
213
     * @param string $parameter
214
     *
215
     * @return string|null
216
     */
217
    public function string(string $parameter): ?string
218
    {
219
        $value = $this->parameters[$parameter] ?? null;
220
221
        if (!is_string($value)) {
222
            $value = null;
223
        }
224
225
        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
226
    }
227
228
    /**
229
     * @param string $parameter
230
     *
231
     * @return array<string>
232
     */
233
    public function requiredArray(string $parameter): array
234
    {
235
        $value = $this->array($parameter);
236
237
        if (is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) is always true.
Loading history...
238
            return $value;
239
        }
240
241
        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
242
    }
243
244
    /**
245
     * @param string $parameter
246
     *
247
     * @return int
248
     */
249
    public function requiredInteger(string $parameter): int
250
    {
251
        $value = $this->integer($parameter);
252
253
        if (is_int($value)) {
254
            return $value;
255
        }
256
257
        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
258
    }
259
260
    /**
261
     * @param string $parameter
262
     *
263
     * @return string
264
     */
265
    public function requiredString(string $parameter): string
266
    {
267
        $value = $this->string($parameter);
268
269
        if (is_string($value)) {
270
            return $value;
271
        }
272
273
        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
274
    }
275
}
276