xref: /webtrees/app/Validator.php (revision fcbce9d892215fd906922a0743351bac0895004e)
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
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees;
21
22use Closure;
23use LogicException;
24use Psr\Http\Message\ServerRequestInterface;
25
26use function array_reduce;
27use function ctype_digit;
28use function is_array;
29use function is_int;
30use function is_string;
31use function parse_url;
32use function str_starts_with;
33
34/**
35 * Validate a parameter from an HTTP request
36 */
37class Validator
38{
39    /** @var array<string|array> */
40    private array $parameters;
41
42    /** @var array<Closure> */
43    private array $rules = [];
44
45    /**
46     * @param array<string|array> $parameters
47     */
48    public function __construct(array $parameters)
49    {
50        $this->parameters = $parameters;
51    }
52
53    /**
54     * @param ServerRequestInterface $request
55     *
56     * @return self
57     */
58    public static function parsedBody(ServerRequestInterface $request): self
59    {
60        return new self((array) $request->getParsedBody());
61    }
62
63    /**
64     * @param ServerRequestInterface $request
65     *
66     * @return self
67     */
68    public static function queryParams(ServerRequestInterface $request): self
69    {
70        return new self($request->getQueryParams());
71    }
72
73    /**
74     * @param int $minimum
75     * @param int $maximum
76     *
77     * @return self
78     */
79    public function isBetween(int $minimum, int $maximum): self
80    {
81        $this->rules[] = static function ($value) use ($minimum, $maximum): ?int {
82            if (is_int($value)) {
83                if ($value < $minimum || $value > $maximum) {
84                    return null;
85                }
86
87                return $value;
88            }
89
90            throw new LogicException('Validator::isBetween() can only be used for integers');
91        };
92
93        return $this;
94    }
95
96    public function localUrl(string $base_url): self
97    {
98        $this->rules[] = static function ($value) use ($base_url): ?string {
99            if (is_string($value)) {
100                $value_info    = parse_url($value);
101                $base_url_info = parse_url($base_url);
102
103                if (!is_array($base_url_info)) {
104                    throw new LogicException(__METHOD__ . ' needs a valid URL');
105                }
106
107                if (is_array($value_info)) {
108                    $scheme_ok = ($value_info['scheme'] ?? 'http') === ($base_url_info['scheme'] ?? 'http');
109                    $host_ok   = ($value_info['host'] ?? '') === ($base_url_info['host'] ?? '');
110                    $port_ok   = ($value_info['port'] ?? '') === ($base_url_info['port'] ?? '');
111                    $user_ok   = ($value_info['user'] ?? '') === ($base_url_info['user'] ?? '');
112                    $path_ok   = str_starts_with($value_info['path'] ?? '/', $base_url_info['path'] ?? '/');
113
114                    if ($scheme_ok && $host_ok && $port_ok && $user_ok && $path_ok) {
115                        return $value;
116                    }
117                }
118
119                return null;
120            }
121
122            throw new LogicException(__METHOD__ . ' can only be used for strings');
123        };
124
125        return $this;
126    }
127
128    /**
129     * @param string $parameter
130     *
131     * @return array<string>
132     */
133    public function array(string $parameter): array
134    {
135        $value = $this->parameters[$parameter] ?? null;
136
137        if (!is_array($value)) {
138            $value = [];
139        }
140
141        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
142    }
143
144    /**
145     * @param string $parameter
146     *
147     * @return int|null
148     */
149    public function integer(string $parameter): ?int
150    {
151        $value = $this->parameters[$parameter] ?? null;
152
153        if (is_string($value) && ctype_digit($value)) {
154            $value = (int) $value;
155        } else {
156            $value = null;
157        }
158
159        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
160    }
161
162    /**
163     * @param string $parameter
164     *
165     * @return string|null
166     */
167    public function string(string $parameter): ?string
168    {
169        $value = $this->parameters[$parameter] ?? null;
170
171        if (!is_string($value)) {
172            $value = null;
173        }
174
175        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
176    }
177}
178