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